Compare commits

...

206 Commits

Author SHA1 Message Date
Alex Orlenko 497d84828a Add CI to build dev docs 2026-02-03 22:40:29 +00:00
Alex Orlenko 0e489901a5 Update debug module
Move debug types from root to new new module.
2026-02-03 22:39:33 +00:00
Alex Orlenko 88063e756f Make table module public 2026-01-30 13:18:46 +00:00
Alex Orlenko c8436e2b80 Make function module public
Reduce number of function-specific types exported to the mlua root and keep
them inside the module.
2026-01-30 13:18:05 +00:00
Alex Orlenko 613748ec16 Use Error::from_lua_conversion helper 2026-01-29 23:09:52 +00:00
Alex Orlenko 2fbd266da6 Remove Error::ToLuaConversionError
This variant used only once and not practically useful.
2026-01-29 23:04:30 +00:00
Alex Orlenko c79b5e9cdb cargo fmt 2026-01-29 23:03:56 +00:00
Alex Orlenko 2ace892613 Rename string::String to LuaString 2026-01-29 18:45:41 +00:00
Alex Orlenko c1ffd4e790 Replace get_or_insert_with with get_or_insert_default 2026-01-29 10:46:58 +00:00
Alex Orlenko d9c139b55f Rust 2024 2026-01-29 10:33:28 +00:00
Alex Orlenko 0c4206c97d Bump min Rust version to 1.88 2026-01-29 10:13:04 +00:00
Alex Orlenko a985dc7a37 Start 0.12.0-dev.1 2026-01-29 10:07:05 +00:00
Alex Orlenko e7fa8d75bb v0.11.6 2026-01-27 16:06:24 +00:00
Alex Orlenko c10718ed2f Update CHANGELOG 2026-01-27 16:04:33 +00:00
Alex Orlenko ec2ce3620f Some final Lua 5.5 updates 2026-01-27 16:00:49 +00:00
Alex Orlenko 71757003c7 mlua-sys: v0.10.0 2026-01-27 15:56:53 +00:00
Alex Orlenko 6bb7f09927 Don't use luaL_typename to get a static type name in Luau.
In Luau this function returns heap-allocated string rather than static string,
so accessing this value when Lua state is destroyed is UB.
Fixes #674
2026-01-27 14:10:43 +00:00
Alex Orlenko e67ae7f0de Make RawLua::{push,push_value,pop_value} public 2026-01-27 12:17:58 +00:00
Alex Orlenko 8c1535c27b cargo fmt 2026-01-27 12:14:50 +00:00
WASDetchan fd245daa6f Expose RawLua via Lua::exec_raw_lua (#670) 2026-01-23 16:19:10 +00:00
Alex Orlenko 7fb7e8685f Update spelling check 2026-01-23 15:37:18 +00:00
Alex Orlenko 93617eef4e Update trybuild (compile) messages 2026-01-23 15:16:35 +00:00
Alex Orlenko 171cdf1758 Some fixes and more tests for Function::info 2026-01-23 12:44:22 +00:00
psentee 3f8f016daa Add num_params, is_vararg, nups to FunctionInfo (#665) 2026-01-23 11:59:59 +00:00
Alex Orlenko 86d0c9bddb More multi borrow "send" test fixes due to regression in Rust 1.93+ 2026-01-22 23:08:17 +00:00
Alex Orlenko 86d63ef27b Fix missing BUFSIZ for wasm32 in libc 2026-01-22 15:46:31 +00:00
Alex Orlenko e33bcf7938 Add Lua 5.5 external string null byte test 2026-01-22 15:32:14 +00:00
Alex Orlenko c80a97b526 Ignore userdata_multithread_access_sync test 2026-01-22 15:25:18 +00:00
Alex Orlenko 3c40cfe199 mlua-sys: Use 2024 edition 2026-01-22 14:09:14 +00:00
Alex Orlenko e9efb73125 Update Luau FFI bindings (added some missing functions) 2026-01-22 14:05:57 +00:00
Alex Orlenko 386c6d8ed8 Update Lua 5.5 FFI bindings (add buffer manipulation, etc) 2026-01-22 12:54:55 +00:00
Alex Orlenko 8f086bf837 Update Lua 5.4 FFI bindings (add buffer manipulation, etc) 2026-01-22 12:50:56 +00:00
Alex Orlenko ad167612dc Update Lua 5.3 FFI bindings (add buffer manipulation, etc) 2026-01-20 15:36:36 +00:00
Alex Orlenko 3366f47d40 Minor Lua 5.2 fixes 2026-01-20 15:28:33 +00:00
Alex Orlenko 0b5ef91f44 Update Lua 5.2 FFI bindings (add buffer manipulation, etc) 2026-01-20 14:29:35 +00:00
Alex Orlenko a162b0ceca Update Lua 5.1 FFI bindings (add buffer manipulation, etc) 2026-01-20 14:29:15 +00:00
Alex Orlenko 8e6d652a21 Bump luau-src to 0.18.0 (Luau 0.705) 2026-01-20 13:05:00 +00:00
Alex Orlenko 1be9e6ce2d Add Lua 5.5 external strings support 2026-01-18 00:03:43 +00:00
Alex Orlenko b1f99aa852 Update tests 2026-01-17 13:26:13 +00:00
Alex Orlenko 4c5465229e Update CI 2026-01-14 21:44:25 +00:00
Alex Orlenko 9b24bb2319 Add Lua 5.5 to CI 2026-01-14 16:01:04 +00:00
Alex Orlenko e1701b6b56 cargo fmt 2026-01-14 15:57:52 +00:00
Alex Orlenko 77d7d5d6bd Add initial Lua 5.5 support 2026-01-13 23:43:31 +00:00
psentee ee9232eda1 AnyUserData::is_proxy (#666) 2026-01-13 15:08:38 +00:00
Alex Orlenko da526595bb mlua-sys: Add Lua 5.5 support 2026-01-13 00:05:01 +00:00
Alex Orlenko 39a7d3b862 Update SYS_MIN_ALIGN 2025-12-31 19:26:38 +02:00
Alex Orlenko 0245d4ce6b v0.11.5 2025-11-22 16:13:08 +00:00
Alex Orlenko a7f105c698 Update Lua::set_memory_category doc 2025-11-22 14:27:02 +00:00
Alex Orlenko d2a8670bef (CI) Update wasi/wasmtime 2025-11-22 13:58:15 +00:00
Alex Orlenko e9de70a030 (CI) Move from x86_64-apple-darwin to aarch64-apple-darwin 2025-11-22 13:57:04 +00:00
Alex Orlenko aee647c6c0 Update CHANGELOG 2025-11-22 13:55:07 +00:00
Alex Orlenko 2e4184e7e4 Update spelling 2025-11-22 13:49:23 +00:00
Alex Orlenko 12b24b6c5b mlua-sys: v0.9.0 2025-11-22 13:47:17 +00:00
Alex Orlenko 1b500b7d47 Remove generic from internal definition of RawLua::create_string 2025-11-22 13:36:50 +00:00
Alex Orlenko ce4fc80e18 Bump luau-src to 0.17.0 (Luau 0.701) 2025-11-22 12:58:59 +00:00
Alex Orlenko 121971f54e Add Lua::set_memory_category and Lua::heap_dump functions to profile Luau memory usage.
This functionality uses Luau private api to dump heap mempory in JSON format for inspection.
The new type `HeapDump` represents memory snapshot with some basic API to calculate stats.
2025-11-22 00:01:34 +00:00
Alex Orlenko 6835537e3b Switch to released verson of luau0-src 2025-11-19 11:25:59 +00:00
Alex Orlenko a2728928cf Temporary disable some send tests on nightly
Aparently there is a regression in the compiler and sync detection does not work correctly
2025-11-18 00:51:24 +00:00
Alex Orlenko 676f3a6983 Fix tests 2025-11-17 13:44:29 +00:00
Alex Orlenko 0beaac228c Update Luau require tests 2025-11-16 23:51:51 +00:00
Alex Orlenko 9a7f75ad6b Update require implementation to satisfy Luau 0.700 2025-11-16 23:13:11 +00:00
Alex Orlenko cd56f92a7f Update Luau definitions in mlua-sys to 0.700 2025-11-16 23:12:06 +00:00
Alex Orlenko 1bd1359f43 Exclude the first arg when checking for yield_with call.
This is part of the previous commit
2025-11-06 23:28:37 +00:00
Alex Orlenko feec72bcbd Reduce number of allocations when calling async function
Instead of creating a uniq poller with upvalue on each async call, return future directly
and pass it to the poller
This also gives about 3-5% perf improvements
2025-11-06 23:09:26 +00:00
Alex Orlenko 0611906c6a Add Lua::type_metatable helper to get metatable of a primitive type.
The accompany function `Lua::set_type_metatable` already exists.
2025-11-05 22:07:14 +00:00
Alex Orlenko 72ac247dca Fix MaybeSend doc 2025-11-04 23:20:53 +00:00
Alex Orlenko f2fd010c5f Add missing lua_remove when discovering function name 2025-10-28 16:04:24 +00:00
Alex Orlenko 0619f264de Add Lua::traceback function to generate stack traces at different levels
This is similar to `debug.traceback`, through does not require debug module.
Close #652
2025-10-28 14:49:22 +00:00
Alex Orlenko ddd44bdd36 Add LUA_LOADED_TABLE constant (Luau) 2025-10-27 20:57:56 +00:00
Alex Orlenko 1152519074 Add add_method_once and add_async_method_once UserData methods (experimental).
They will allow implementing userdata methods that can be called only once, destructing
userdata instance during the call.
2025-10-26 20:14:46 +00:00
Alex Orlenko 3a2fd1ec59 Make AnyUserData::type_name public 2025-10-17 20:00:53 +01:00
Alex Orlenko a4c8b20697 impl IntoLuaMulti for &MultiValue 2025-10-13 12:36:42 +01:00
Alex Crichton 6e353d6c9f Build/test wasm32-wasip2 in CI (#649)
This is a follow-up from mlua-rs/lua-src-rs#13 which verifies/tests that
mlua/lua all work when compiled for a WASI target. While this doesn't
have formal documentation yet it also codifies in CI configuration how
to build for WASI and get tests passing (notably C compiler
configuration and some misc Rust flags).

This moves some `dev-dependencies` that don't compile for
`wasm32-wasip2` to a different section of the manifest. This
additionally annotates panicking tests with `#[cfg(not(panic =
"abort"))]` to skip those tests on WASI.

This does not test either the `send` or `async` feature at this time.
Testing `send` requires threads which WASI does not yet support, and
testing `async` requires more support in Tokio which is not currently
there yet.
2025-10-04 09:52:23 +01:00
Alex Orlenko 247208edb1 v0.11.4 2025-09-28 23:46:55 +01:00
Alex Orlenko e08768cc5e Derive Default for Value (clippy) 2025-09-28 23:42:12 +01:00
Alex Orlenko 5b38af9746 AsyncCallFuture is Unpin 2025-09-19 10:00:28 +01:00
Alex Orlenko 54907f80c5 Add SerializableValue to lib and prelude exports 2025-09-12 12:40:43 +01:00
Alex Orlenko ae512f2b49 Remove const from SerializableValue (it's not really useful) 2025-09-12 12:40:00 +01:00
Alex Orlenko 53c159b6cb Unhide Value::to_serializable 2025-09-12 11:49:43 +01:00
Alex Orlenko 2beca6ebe1 Add test for Table::for_each_value 2025-09-12 11:49:37 +01:00
Alex Orlenko 09da7a41e5 Add new serde option "detect_mixed_tables"
This option would allow detecting mixed tables (with array-like and map-like entries or several borders)
to encoding them chosing the best method (as a map or as a table).
2025-09-12 11:11:18 +01:00
Alex Orlenko bad20374ad Simplify Table::clear method
There is no need to traverse array part, lua_next will cover everything
2025-09-08 23:37:28 +01:00
Alex Orlenko 40b507c3ec Add ObjectLike::get_path helper 2025-09-04 19:12:44 +01:00
Andrew Dunbar 537cc995f6 Copyedit English in README.md (#639) 2025-09-04 14:59:24 +01:00
Alex Orlenko 5d27cb91b2 Add optional __namecall optimization for Luau
Add `UserDataRegistry::enable_namecall()` hint to set `__namecall` metamethod to enable Luau-specific method resolution optimization.
2025-09-02 00:53:12 +01:00
Alex Orlenko c70a636ca9 Remove newlines from yield_with examples 2025-08-30 12:51:53 +01:00
Alex Orlenko 13ff0ca798 v0.11.3 2025-08-29 23:11:21 +01:00
Alex Orlenko 44f49e35d6 Update CHANGELOG 2025-08-29 00:18:06 +01:00
Alex Orlenko e1ee4058a6 Add new benchmark to measure complex userdata method calls 2025-08-28 23:56:03 +01:00
Alex Orlenko f06d0020ea Add test to emulate method through field 2025-08-28 23:50:18 +01:00
Alex Orlenko d399559d30 Add Lua::yield_with to allow yielding Rust async functions and exchange values between Lua coroutine and Rust.
This functionality is similar to `coroutine.yield` and `coroutine.resume` without C restrictions.
2025-08-28 18:41:24 +01:00
Alex Orlenko 30735d5ff1 Fix thread recovery when pushing a bad arg
We should not erase thread stack if a bad argument is pushed before resuming the thread.
2025-08-25 23:07:37 +01:00
Alex Orlenko 75c23e5853 Add lua_cpcall to Luau ffi (0.688+) 2025-08-25 12:54:17 +01:00
Alex Orlenko 347856b806 Do not try to yield at non-yielable points in Luau interrupt
In particular we cannot yeild across metamethod/C-call boundaries.
This behaviour matches with Lua 5.3+ yielding from hooks only at safe points.
Closes #632
2025-08-25 12:19:50 +01:00
Alex Orlenko 774a63bece Add Buffer::cursor() method
This can be useful for providing access to buffers through core IO traits.
2025-08-24 11:29:01 +01:00
Alex Orlenko c481c87eac Add Lua::create_buffer_with_capacity method
This allow creating a preallocated buffer with specified size initialized to zero.
2025-08-23 22:38:55 +01:00
Alex Orlenko 85b280a9d6 Update nightly Rust error message matching 2025-08-23 09:40:13 +01:00
Alex Orlenko db7b782d3c Remove lifetimes from short type names 2025-08-23 09:13:31 +01:00
Alex Orlenko 5f38445558 Fix warnings 2025-08-20 16:25:06 +01:00
Alex Orlenko df0a44d405 Make Lua reference values cheap to clone
Instead of locking the VM and making a copy on auxiliary thread, track number of references using Rust ref counter.
This should also help reducing number of used references (they are limited to to 1M usually) on auxiliary thread.
2025-08-20 12:05:37 +01:00
Alex Orlenko f0806a6d62 Lower fastpath table creation limit to 1 << 26
When Lua is configured without memory restrictions, we use fastpath for table creation (unprotected mode).
In generally it's safe as long as we `abort()` on allocation failure.
However some Lua versions have additional restrictions on table size that we need to adhere in mlua too.
Probably Luau has the lowest limits.
Fixes #627
2025-08-13 22:49:40 +01:00
Alex Orlenko 3516f4c6ca v0.11.2 2025-08-10 00:53:45 +01:00
Alex Orlenko ca73583714 Update CHANGELOG 2025-08-10 00:53:01 +01:00
Alex Orlenko 36560435f7 Add push_into_stack_multi fastpath to Variadic 2025-08-10 00:35:51 +01:00
Alex Orlenko 763c2b2564 Update repl example: don't print newline if no values returned 2025-08-10 00:20:20 +01:00
Alex Orlenko bafdb6138c Update dependencies 2025-08-10 00:19:54 +01:00
Alex Orlenko c9d6a610e1 mlua-sys: v0.8.3 2025-08-10 00:11:05 +01:00
Alex Orlenko bd63f63bc9 Use ascii lowercase for module aliases
This matches with Luau 0.686 changes
2025-08-09 19:14:31 +01:00
piz-ewing c035c23a15 fix: normalize_chunk_name handles Windows paths with drive letter (#623)
Co-authored-by: ewing <ewing@MacBook-Pro.local>
2025-08-04 22:34:36 +01:00
Alex Orlenko cb153a52b2 Make Luau registered aliases case-insensitive
Executing `require("@my_module")` or `require("@My_Module")` should give the same result and use case-insensitive name.
See #620 for details
2025-07-26 22:23:16 +01:00
Alex Orlenko b1c69d3005 Use to_bits comparison to check if a float value can be represented as an integer losslessly.
This allows to simplify the code while still maintaining "negative zeros" edge case.
Thanks @JasonHise for the suggestion.
2025-07-25 21:25:08 +01:00
Alex Orlenko 841bd332e4 Fix LuaJIT negative zero tests 2025-07-25 15:24:04 +01:00
Alex Orlenko 815d1bd7c9 Better handling negative zeros to match Lua 5.3+ behavior
In Lua 5.3+ the function `lua_isinteger` returns "false" for -0.0 numbers.
In earlier Lua versions we should follow the same behavior to avoid losing the sign when converting to Integer.
Close #618
2025-07-25 14:32:47 +01:00
Alex Orlenko 78331ceebe v0.11.1 2025-07-15 22:43:18 +01:00
Alex Orlenko f945a35cbd Execute metatable destructor in Table::set_metatable at the end of invocation
Before this change, destructor was executed shortly after pushing metatable to ref_thread.
2025-07-15 19:14:46 +01:00
Alex Orlenko 459edb6816 Always grow aux ref stack considering the reserve 2025-07-15 16:32:22 +01:00
Alex Orlenko 00328b0b64 Protect Lua::push_c_function for Lua <5.2 2025-07-15 16:11:31 +01:00
Alex Orlenko 928d94d255 v0.11.0 2025-07-14 15:33:02 +01:00
Alex Orlenko 583c35a172 Prepare for v0.11.0 2025-07-12 22:47:20 +01:00
Alex Orlenko 1791c599f4 mlua-sys: v0.8.2 2025-07-12 22:46:37 +01:00
Alex Orlenko 1e48817a64 Fix deregistering previously-registered userdata 2025-07-12 19:20:15 +01:00
Alex Orlenko 8d219503dd Opt-out from R: MaybeSend in AsyncThread<R> 2025-07-12 15:34:04 +01:00
Alex Orlenko 95367855c1 Return AsyncCallFuture<R> instead of opaque impl Future from ObjectLike trait. 2025-07-12 15:30:58 +01:00
Alex Orlenko 49389c4aa4 Wrap Function::coverage callback to RefCell (Luau) 2025-07-12 13:34:39 +01:00
Alex Orlenko 13dc2b5352 Don't release Lua lock prematurely when when accessing Buffer bytes (Luau) 2025-07-12 13:08:33 +01:00
Alex Orlenko 7afbf74128 Add MaybeSend bound to async methods on ObjectLike trait (sealed) 2025-07-12 12:45:36 +01:00
Alex Orlenko 06c3bd9d69 Fix serde README section (close #613) 2025-07-12 11:50:11 +01:00
Alex Orlenko 1ddaea60ce Bump luau-src to 0.15.4+luau682 2025-07-12 11:27:41 +01:00
Alex Orlenko a653d08768 Simplify Compiler::add_library_constant (combine lib and member) 2025-07-09 00:11:46 +01:00
Alex Orlenko 2b6b0144a1 Merge Compiler::set_vector_lib into set_vector_ctor 2025-07-08 23:55:45 +01:00
Alex Orlenko 4cfe0be945 Update CHANGELOG 2025-07-08 23:22:18 +01:00
Alex Orlenko a9a4814c3c Use StdString for consistency in chunk.rs 2025-07-08 22:25:23 +01:00
Alex Orlenko b1f73ec29d Update Luau Compiler methods to better control extra options:
- Add `add_mutable_global`
- Add `add_userdata_type`
- Replace `set_library_constants` with `add_library_constant`
- Add `add_disabled_builtin`
2025-07-08 22:22:44 +01:00
Alex Orlenko 61a2141151 Don't panic when fourth library searcher does not exists.
When disabling C modules, we remove the last two searchers (C & C all-in-one).
In Pluto the C searches may not exist by design, in this case check that 4th searcher is present before removing it.
Closes #530
2025-07-08 13:37:48 +01:00
Alex Orlenko dea38f27a5 Change !cfg!(..) to cfg!(not(..)) for better readability 2025-07-08 10:42:15 +01:00
Sculas 04aaa18dc8 feat: Allow external build scripts to link Lua libraries (#529)
Allow external build scripts to link Lua libraries
2025-07-08 10:30:26 +01:00
Alex Orlenko d8455c038a Update CHANGELOG 2025-07-08 10:12:03 +01:00
Alex Orlenko ca22ea3be7 Deprecate Debug::curr_line() in favour of Debug::current_line() that returns Option 2025-07-08 10:09:26 +01:00
Alex Orlenko cf05593d66 Fix Debug::is_tail_call 2025-07-08 10:05:37 +01:00
Alex Orlenko d011a1f851 Update CHANGELOG 2025-07-07 23:15:46 +01:00
Alex Orlenko 1ec4661bf9 mlua_derive: v0.11.0 2025-07-07 22:58:41 +01:00
Alex Orlenko d3b2999d2f Remove MaybeSend requirement from Require trait and add to Lua::create_require_function instead 2025-07-07 22:58:24 +01:00
Alex Orlenko 72f6536efb Check table requested capacity limits before enabling unprotected mode.
Lua tables have limits and can overflow, which must be captured in protected mode.
2025-07-07 22:57:30 +01:00
Alex Orlenko 646827a6bb Update Table::set_metatable
- Return Err (instead of panic) when trying to change readonly table (Luau)
- Slightly optimize performance
2025-07-06 11:37:02 +01:00
Alex Orlenko 1882931cd9 Optimize Table::metatable 2025-07-06 10:57:26 +01:00
Alex Orlenko c90cac5189 Add Lua::set_globals method to replace global environment.
Closes #611
2025-07-06 10:57:25 +01:00
Alex Orlenko c0d839d8d2 Make Thread::state pub (hidden) 2025-07-05 22:36:47 +01:00
Alex Orlenko 80471c6dad Optimize AnyUserData::metatable 2025-07-05 11:33:11 +01:00
Alex Orlenko 4b9d1cf271 Replace impl ToString with Into<StdString>
This is a more canonical way to accept any types of stirng but not arbitrary types that implement `Display`
2025-07-05 11:21:18 +01:00
Alex Orlenko 55c07f3b28 Some minor changes in Luau TextRequirer (comments, naming, etc) 2025-07-03 14:42:24 +01:00
Alex Orlenko ef4eabd327 Don't use Value::as_str() internally 2025-07-01 22:51:16 +01:00
Alex Orlenko a3302afdc1 Deprecate Value::as_str and Value::as_string_lossy
These methods don't follow Rust naming convention, see
https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
2025-07-01 22:35:40 +01:00
Alex Orlenko dfb4e9a668 Fix LuaJIT stack inspection tests 2025-07-01 21:40:06 +01:00
Alex Orlenko 92db0f6d3a Fix Lua::inspect_stack callback proto 2025-06-30 23:28:04 +01:00
Alex Orlenko a3697ab1db Add Debug::function method to get function running at a given level.
Close #607
2025-06-30 23:25:36 +01:00
Alex Orlenko 052740db15 Save lua_State at the moment of constructing Debug instead of resolving it dynamically 2025-06-30 22:38:46 +01:00
Alex Orlenko faf547c154 Refactor Lua::inspect_stack and debug interface.
It was possible to cause a crash when getting a `Debug` instance and keeping it while deallocating the Lua stack frames.
2025-06-30 12:21:10 +01:00
Alex Orlenko 0de7cd1c7d Don't move or wrap ffi::lua_Debug struct when inspecting stack
This can cause a crash if `ffi::lua_Debug` changed between `lua_getstack` and `lua_getinfo` calls.
Fixes #610
2025-06-29 11:49:34 +01:00
Alex Orlenko 58953e5635 v0.11.0-beta.3 2025-06-23 23:23:07 +01:00
Alex Orlenko 6406de405d mlua-sys: v0.8.1 2025-06-23 23:22:53 +01:00
Alex Orlenko 727096dd3b Handle OOM error during luau_load (Luau >= 0.679) 2025-06-23 23:22:36 +01:00
Alex Orlenko 2445230759 Update CHANGELOG 2025-06-23 22:44:36 +01:00
Alex Orlenko 1cd2bdc808 Ignore test_async_require on windows 2025-06-23 16:37:46 +01:00
Alex Orlenko f8ed33a2aa Fix tests 2025-06-23 16:23:53 +01:00
Alex Orlenko 25955893e0 (Luau Require) Resolve Lua file path relative to the current directory
and unrelated to Rust source file location.
When a Lua file is required inside a Rust file (in a chunk), we should resolve the Lua file relative to the current directory,
instead of relative to the Rust chunk path.
The Rust file location is an internal information that does not exist when the compiled binary runs.
Fixes #605
2025-06-23 15:53:35 +01:00
Alex Orlenko 3f0c69b70b Fix logic to terminate futures on drop.
The underlying Lua thread must stay in yielded state rather than finished.
2025-06-17 15:25:21 +01:00
Alex Orlenko 9da98d42c7 Move ref_stack_pop into ExtraData method. 2025-06-16 22:25:26 +01:00
Alex Orlenko f539f60987 Fix Function::deep_clone() method (Luau).
The `lua_clonefunction` function can fail (and trigger GC) so we need to return Result instead of allowing longjmp
2025-06-16 21:41:29 +01:00
Alex Orlenko aa187e6663 Increase REF_STACK_RESERVE to 3 slots 2025-06-16 21:37:16 +01:00
Alex Orlenko ec10bf2a39 Revert 2e0e86dab2 (Update stack_value helper) 2025-06-16 21:33:47 +01:00
Alex Orlenko 3ea80b763d cargo fmt 2025-06-16 11:21:51 +01:00
Alex Orlenko 2e0e86dab2 Update stack_value helper.
It uses zero stack spaces in Luau, and ref thread for `WrappedError` check in ther Lua versions.
Close #597
2025-06-16 11:12:53 +01:00
Alex Orlenko 0ac7aebb27 Update Lua::sandbox doc 2025-06-13 15:51:31 +01:00
Alex Orlenko 634e5d4550 Reduce collectgarbage options in sandboxed mode
See https://luau.org/sandbox#library
2025-06-13 15:47:54 +01:00
Alex Orlenko 05d6c20520 One more mismatched-lifetime-syntaxes 2025-06-12 23:44:47 +01:00
Alex Orlenko 63e7cfd31b Satisfy mismatched-lifetime-syntaxes lint (nightly)
See rust-lang/rust#141787
2025-06-12 16:30:48 +01:00
Alex Orlenko 9c24c99cbe v0.11.0-beta.2 2025-06-12 14:12:46 +01:00
Alex Orlenko 62f84828f2 Open some doc(hidden) functionality 2025-06-12 13:42:26 +01:00
Alex Orlenko 7bc72be7d3 Use serde feature flag instead of serialize.
The old one is still supported.
2025-06-12 13:35:22 +01:00
Ron Tseytlin 2fbbbe4238 Fix minor grammar mistakes in README.md (#591) 2025-06-12 12:57:11 +01:00
Alex Orlenko f00208373e Bump lua-src 2025-06-12 00:17:29 +01:00
Alex Orlenko a2dc662a92 Add RawLua::create_table_from (internal) 2025-06-11 23:55:06 +01:00
Alex Orlenko caeac2e9a3 Add private app_data container for mlua internal use 2025-06-11 22:24:26 +01:00
Alex Orlenko 3d5261640d Fix doc warnings 2025-06-06 23:25:36 +01:00
Alex Orlenko b57a6239a6 mlua_derive: v0.11.0-beta.2 2025-06-06 23:20:01 +01:00
Alex Orlenko 031424f6ce Update dev dependencies 2025-06-06 23:17:58 +01:00
Alex Orlenko c4c9609ac6 mlua-sys: v0.8.0 2025-06-06 23:09:30 +01:00
Alex Orlenko 6fcd18e434 Update CHANGELOG 2025-06-06 16:03:59 +01:00
Alex Orlenko 39cac5699a Add unwinding support (returning an Error) to Luau Require implementation 2025-06-06 13:46:51 +01:00
Alex Orlenko e6e1ef014f Some cosmetic changes (Luau "require") 2025-06-05 23:11:14 +01:00
Alex Orlenko 942a443592 Bump Lua 5.4 to 5.4.8 2025-06-05 15:02:47 +01:00
Alex Orlenko 65e292dac4 More Luau require tests 2025-06-03 23:01:50 +01:00
Alex Orlenko 1a82f83644 Move parking_lot/send_guard from send to userdata-wrappers feature.
Related to #553
2025-05-30 23:20:06 +01:00
Alex Orlenko 38fbd08c72 Use luau0-src v0.15.0 (Luau 0.676) 2025-05-30 21:56:27 +01:00
Alex Orlenko b3854d2f1d Add '=' prefix to __mlua_bind name 2025-05-28 14:03:10 +01:00
Alex Orlenko 00a56b115b Add LuaBorrowedBytes/LuaBorrowedStr to prelude 2025-05-28 14:01:35 +01:00
Alex Orlenko 2fefaafaa6 Update "AnyUserData::take" to work on ref thread without need to push into stack. 2025-05-28 12:13:34 +01:00
Alex Orlenko 76a8f8cc71 Add __type to Error's userdata metatable.
Close #585
2025-05-27 01:31:54 +01:00
Alex Orlenko 13395e9c3d Sync mlua_derive with AsChunk trait 2025-05-27 00:54:24 +01:00
Alex Orlenko c61219dd93 Update Luau Require trait (sync with 0.674) 2025-05-27 00:12:45 +01:00
Alex Orlenko 2e7c654cfe Make AsChunk trait dyn-friendly 2025-05-26 23:29:59 +01:00
Alex Orlenko f36aaa5ce1 Add loadstring function to Luau
Closes #578
2025-05-15 11:10:42 +01:00
Alex Orlenko df38878278 Update __mlua_index/__mlua_newindex chunk names 2025-05-15 11:03:30 +01:00
Alex Orlenko c5c1fe3b85 Fix tests 2025-05-14 00:19:29 +01:00
Alex Orlenko 0cc4b15f6b Generate doc for lua_module macro using doc cfg instead of docsrs 2025-05-13 21:56:11 +01:00
Alex Orlenko f16aca687d Terminate underlying Rust future when AsyncThread is dropped.
Before this change, Lua GC was responsible to collect and destroy the future if
`AsyncThread` dropped in yielded state.
Now we will propagate "drop" event immediately so Lua GC need to only free the memory.
2025-05-09 12:48:40 +01:00
148 changed files with 7317 additions and 2512 deletions
+68
View File
@@ -0,0 +1,68 @@
name: Documentation (dev)
on:
push:
branches: [dev]
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment
concurrency:
group: pages
cancel-in-progress: true
jobs:
build:
name: Build Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
- name: Build documentation
env:
RUSTDOCFLAGS: "--cfg docsrs"
run: |
cargo +nightly doc --no-deps \
--features "lua55,vendored,async,send,serde,macros,anyhow,userdata-wrappers"
- name: Create index redirect
run: |
echo '<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Redirecting to mlua documentation</title>
<meta http-equiv="refresh" content="0; URL=mlua/index.html">
<link rel="canonical" href="mlua/index.html">
</head>
<body>
<p>Redirecting to <a href="mlua/index.html">mlua documentation</a>...</p>
</body>
</html>' > target/doc/index.html
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
with:
path: target/doc
deploy:
name: Deploy to GitHub Pages
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+63 -44
View File
@@ -9,12 +9,12 @@ jobs:
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable] rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include: include:
- os: ubuntu-latest - os: ubuntu-latest
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
- os: macos-latest - os: macos-latest
target: x86_64-apple-darwin target: aarch64-apple-darwin
- os: windows-latest - os: windows-latest
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
steps: steps:
@@ -27,39 +27,23 @@ jobs:
- name: Build ${{ matrix.lua }} vendored - name: Build ${{ matrix.lua }} vendored
run: | run: |
cargo build --features "${{ matrix.lua }},vendored" cargo build --features "${{ matrix.lua }},vendored"
cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers" cargo build --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers"
cargo build --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send" cargo build --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send"
shell: bash shell: bash
- name: Build ${{ matrix.lua }} pkg-config - name: Build ${{ matrix.lua }} pkg-config
if: ${{ matrix.os == 'ubuntu-latest' }} if: ${{ matrix.os == 'ubuntu-latest' && matrix.lua != 'lua55' }}
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends liblua5.4-dev liblua5.3-dev liblua5.2-dev liblua5.1-0-dev libluajit-5.1-dev sudo apt-get install -y --no-install-recommends liblua5.4-dev liblua5.3-dev liblua5.2-dev liblua5.1-0-dev libluajit-5.1-dev
cargo build --features "${{ matrix.lua }}" cargo build --features "${{ matrix.lua }}"
build_aarch64_cross_macos:
name: Cross-compile to aarch64-apple-darwin
runs-on: macos-latest
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit]
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
target: aarch64-apple-darwin
- name: Cross-compile
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers"
build_aarch64_cross_ubuntu: build_aarch64_cross_ubuntu:
name: Cross-compile to aarch64-unknown-linux-gnu name: Cross-compile to aarch64-unknown-linux-gnu
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build needs: build
strategy: strategy:
matrix: matrix:
lua: [lua54, lua53, lua52, lua51, luajit] lua: [lua55, lua54, lua53, lua52, lua51, luajit]
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
@@ -72,7 +56,7 @@ jobs:
sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross
shell: bash shell: bash
- name: Cross-compile - name: Cross-compile
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
shell: bash shell: bash
build_armv7_cross_ubuntu: build_armv7_cross_ubuntu:
@@ -81,7 +65,7 @@ jobs:
needs: build needs: build
strategy: strategy:
matrix: matrix:
lua: [lua54, lua53, lua52, lua51] lua: [lua55, lua54, lua53, lua52, lua51]
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
@@ -94,7 +78,7 @@ jobs:
sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross
shell: bash shell: bash
- name: Cross-compile - name: Cross-compile
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
shell: bash shell: bash
test: test:
@@ -105,12 +89,12 @@ jobs:
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable, nightly] rust: [stable, nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4] lua: [lua55, lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4]
include: include:
- os: ubuntu-latest - os: ubuntu-latest
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
- os: macos-latest - os: macos-latest
target: x86_64-apple-darwin target: aarch64-apple-darwin
- os: windows-latest - os: windows-latest
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
steps: steps:
@@ -123,14 +107,14 @@ jobs:
- name: Run ${{ matrix.lua }} tests - name: Run ${{ matrix.lua }} tests
run: | run: |
cargo test --features "${{ matrix.lua }},vendored" cargo test --features "${{ matrix.lua }},vendored"
cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers" cargo test --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers"
cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send" cargo test --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send"
shell: bash shell: bash
- name: Run compile tests (macos lua54) - name: Run compile tests (macos lua55)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }} if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua55' }}
run: | run: |
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" -- --ignored TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" --tests -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros" -- --ignored TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serde,macros" --tests -- --ignored
shell: bash shell: bash
test_with_sanitizer: test_with_sanitizer:
@@ -141,7 +125,7 @@ jobs:
matrix: matrix:
os: [ubuntu-latest] os: [ubuntu-latest]
rust: [nightly] rust: [nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include: include:
- os: ubuntu-latest - os: ubuntu-latest
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
@@ -154,8 +138,8 @@ jobs:
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- name: Run ${{ matrix.lua }} tests with address sanitizer - name: Run ${{ matrix.lua }} tests with address sanitizer
run: | run: |
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
shell: bash shell: bash
env: env:
RUSTFLAGS: -Z sanitizer=address RUSTFLAGS: -Z sanitizer=address
@@ -168,7 +152,7 @@ jobs:
matrix: matrix:
os: [ubuntu-latest] os: [ubuntu-latest]
rust: [nightly] rust: [nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include: include:
- os: ubuntu-latest - os: ubuntu-latest
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
@@ -181,7 +165,7 @@ jobs:
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- name: Run ${{ matrix.lua }} tests with forced memory limit - name: Run ${{ matrix.lua }} tests with forced memory limit
run: | run: |
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
shell: bash shell: bash
env: env:
RUSTFLAGS: --cfg=force_memory_limit RUSTFLAGS: --cfg=force_memory_limit
@@ -194,12 +178,12 @@ jobs:
matrix: matrix:
os: [ubuntu-latest, macos-latest] os: [ubuntu-latest, macos-latest]
rust: [stable] rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit] lua: [lua55, lua54, lua53, lua52, lua51, luajit]
include: include:
- os: ubuntu-latest - os: ubuntu-latest
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
- os: macos-latest - os: macos-latest
target: x86_64-apple-darwin target: aarch64-apple-darwin
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
@@ -240,7 +224,7 @@ jobs:
needs: build needs: build
strategy: strategy:
matrix: matrix:
lua: [lua54, lua53, lua52, lua51, luau] lua: [lua55, lua54, lua53, lua52, lua51, luau]
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
@@ -254,7 +238,42 @@ jobs:
- name: Run ${{ matrix.lua }} tests - name: Run ${{ matrix.lua }} tests
run: | run: |
cargo test --tests --features "${{ matrix.lua }},vendored" cargo test --tests --features "${{ matrix.lua }},vendored"
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,anyhow,userdata-wrappers" cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers"
test_wasm32_wasip2:
name: Test on wasm32-wasip2
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
lua: [lua55, lua54, lua53, lua52, lua51]
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly-2025-10-02
target: wasm32-wasip2
- name: Install wasi-sdk/Wasmtime
working-directory: ${{ runner.tool_cache }}
run: |
wasi_sdk=29
wasmtime=v40.0.1
curl -LO https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$wasi_sdk/wasi-sdk-$wasi_sdk.0-x86_64-linux.tar.gz
tar xf wasi-sdk-$wasi_sdk.0-x86_64-linux.tar.gz
WASI_SDK_PATH=`pwd`/wasi-sdk-$wasi_sdk.0-x86_64-linux
echo "WASI_SDK_PATH=$WASI_SDK_PATH" >> $GITHUB_ENV
echo "CC_wasm32_wasip2=$WASI_SDK_PATH/bin/clang" >> $GITHUB_ENV
echo "CARGO_TARGET_WASM32_WASIP2_LINKER=$WASI_SDK_PATH/bin/clang" >> $GITHUB_ENV
echo "CARGO_TARGET_WASM32_WASIP2_RUSTFLAGS=-Clink-arg=-Wl,--export=cabi_realloc" >> $GITHUB_ENV
curl -LO https://github.com/bytecodealliance/wasmtime/releases/download/$wasmtime/wasmtime-$wasmtime-x86_64-linux.tar.xz
tar xf wasmtime-$wasmtime-x86_64-linux.tar.xz
echo "CARGO_TARGET_WASM32_WASIP2_RUNNER=`pwd`/wasmtime-$wasmtime-x86_64-linux/wasmtime -W exceptions" >> $GITHUB_ENV
- name: Run ${{ matrix.lua }} tests
run: |
cargo test --target wasm32-wasip2 --tests --features "${{ matrix.lua }},vendored"
cargo test --target wasm32-wasip2 --tests --features "${{ matrix.lua }},vendored,serde,macros,anyhow,userdata-wrappers"
rustfmt: rustfmt:
name: Rustfmt name: Rustfmt
@@ -271,7 +290,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4] lua: [lua55, lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
steps: steps:
- uses: actions/checkout@main - uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
@@ -281,4 +300,4 @@ jobs:
- uses: giraffate/clippy-action@v1 - uses: giraffate/clippy-action@v1
with: with:
reporter: 'github-pr-review' reporter: 'github-pr-review'
clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,anyhow,userdata-wrappers" clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
+10 -5
View File
@@ -1,17 +1,22 @@
name: Typos Check name: Spelling Check
on: on:
pull_request: pull_request:
workflow_dispatch: workflow_dispatch:
permissions:
contents: read
env:
CLICOLOR: 1
jobs: jobs:
run: spelling:
name: Spell Check with Typos name: Spell Check with Typos
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Actions Repository - name: Checkout Actions Repository
uses: actions/checkout@v4 uses: actions/checkout@main
- name: Check spelling - name: Check spelling
uses: crate-ci/typos@master uses: crate-ci/typos@v1.42.1
with: with:
config: ./typos.toml config: ./typos.toml
+78
View File
@@ -1,3 +1,81 @@
## v0.11.6 (Jan 27, 2026)
- Added Lua 5.5 support (`lua55` feature flag)
- Luau updated to 0.705+
- Added `AnyUserData::is_proxy` method to check if userdata is a proxy
- Added `num_params`, `num_upvalues`, `is_vararg` to `FunctionInfo`
## v0.11.5 (Nov 22, 2025)
- Luau updated to 0.701
- Added `Lua::set_memory_category` and `Lua::heap_dump` functions to profile (Luau) memory
- Added `Lua::type_metatable` helper to get metatable of a primitive type
- Added `Lua::traceback` function to generate stack traces at different levels
- Added `add_method_once` /`add_async_method_once` UserData methods (experimental)
- Make `AnyUserData::type_name` public
- impl `IntoLuaMulti` for `&MultiValue`
- Bugfixes and async perf improvements
## v0.11.4 (Sep 29, 2025)
- Make `Value::to_serializable` public
- Add new serde option `detect_mixed_tables` (to encode mixed array+map tables)
- Add `ObjectLike::get_path` helper (for tables and userdata)
## v0.11.3 (Aug 30, 2025)
- Add `Lua::yield_with` to use as `coroutine.yield` functional replacement in async functions for any Lua
- Do not try to yield at non-yielable points in Luau interrupt (#632)
- Add `Buffer::cursor` method (Luau)
- Add `Lua::create_buffer_with_capacity` method (Luau)
- Make Lua reference values cheap to clone (only increments ref count)
- Fix panic on large (>67M entries) table creation
## v0.11.2 (Aug 10, 2025)
- Faster stack push for `Variadic<T>`
- Fix handling Windows paths with drive letter in Luau require (#623)
- Make Luau registered aliases ascii case-insensitive (#620)
- Fix deserializing negative zeros `-0.0` (#618)
## v0.11.1 (Jul 15, 2025)
- Fixed bug exhausting Lua auxiliary stack and leaving it without reserve (#615)
- `Lua::push_c_function` now correctly handles OOM for Lua 5.1 and Luau
## v0.11.0 (Jul 14, 2025)
Changes since v0.11.0-beta.3
- Allow linking external Lua libraries in a build script (e.g. pluto) using `external` mlua-sys feature flag
- `Lua::inspect_stack` takes a callback with `&Debug` argument, instead of returning `Debug` directly
- Added `Debug::function` method to get function running at a given level
- `Debug::curr_line` is deprecated in favour of `Debug::current_line` that returns `Option<usize>`
- Added `Lua::set_globals` method to replace global environment
- `Table::set_metatable` now returns `Result<()>` (this operation can fail in sandboxed Luau mode)
- `impl ToString` replaced with `Into<StdString>` in `UserData` registration
- `Value::as_str` and `Value::as_string_lossy` methods are deprecated (as they are non-idiomatic)
- Bugfixes and improvements
## v0.11.0-beta.3 (Jun 23, 2025)
- Luau in sandboxed mode has reduced options in `collectgarbage` function (to follow the official doc)
- `Function::deep_clone` now returns `Result<Function>` as this operation can trigger memory errors
- Luau "Require" resolves included Lua files relative to the current directory (#605)
- Fixed bug when finalizing `AsyncThread` on drop (`call_async` methods family)
## v0.11.0-beta.2 (Jun 12, 2025)
- Lua 5.4 updated to 5.4.8
- Terminate Rust `Future` when `AsyncThread` is dropped (without relying on Lua GC)
- Added `loadstring` function to Luau
- Make `AsChunk` trait dyn-friendly
- Luau `Require` trait synced with Luau 0.674
- Luau `Require` trait methods now can return `Error` variant (in `NavigateError` enum)
- Added `__type` to `Error`'s userdata metatable (for `typeof` function)
- `parking_log/send_guard` is moved to `userdata-wrappers` feature flag
- New `serde` feature flag to replace `serialize` (the old one is still available)
## v0.11.0-beta.1 (May 7th, 2025) ## v0.11.0-beta.1 (May 7th, 2025)
- New "require-by-string" for Luau (with `Require` trait and async support) - New "require-by-string" for Luau (with `Require` trait and async support)
+28 -24
View File
@@ -1,9 +1,9 @@
[package] [package]
name = "mlua" name = "mlua"
version = "0.11.0-beta.1" # remember to update mlua_derive version = "0.12.0-dev.1" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"] authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.79.0" rust-version = "1.88"
edition = "2021" edition = "2024"
repository = "https://github.com/mlua-rs/mlua" repository = "https://github.com/mlua-rs/mlua"
documentation = "https://docs.rs/mlua" documentation = "https://docs.rs/mlua"
readme = "README.md" readme = "README.md"
@@ -11,12 +11,12 @@ keywords = ["lua", "luajit", "luau", "async", "scripting"]
categories = ["api-bindings", "asynchronous"] categories = ["api-bindings", "asynchronous"]
license = "MIT" license = "MIT"
description = """ description = """
High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau High level bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including LuaJIT) and Luau
with async/await features and support of writing native Lua modules in Rust. with async/await features and support of writing native Lua modules in Rust.
""" """
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["lua54", "vendored", "async", "send", "serialize", "macros"] features = ["lua55", "vendored", "async", "send", "serde", "macros"]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[workspace] [workspace]
@@ -26,6 +26,7 @@ members = [
] ]
[features] [features]
lua55 = ["ffi/lua55"]
lua54 = ["ffi/lua54"] lua54 = ["ffi/lua54"]
lua53 = ["ffi/lua53"] lua53 = ["ffi/lua53"]
lua52 = ["ffi/lua52"] lua52 = ["ffi/lua52"]
@@ -36,17 +37,20 @@ luau = ["ffi/luau"]
luau-jit = ["luau", "ffi/luau-codegen"] luau-jit = ["luau", "ffi/luau-codegen"]
luau-vector4 = ["luau", "ffi/luau-vector4"] luau-vector4 = ["luau", "ffi/luau-vector4"]
vendored = ["ffi/vendored"] vendored = ["ffi/vendored"]
module = ["dep:mlua_derive", "ffi/module"] module = ["mlua_derive", "ffi/module"]
async = ["dep:futures-util"] async = ["dep:futures-util"]
send = ["parking_lot/send_guard", "error-send"] send = ["error-send"]
error-send = [] error-send = []
serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"] serde = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"]
macros = ["mlua_derive/macros"] macros = ["mlua_derive/macros"]
anyhow = ["dep:anyhow", "error-send"] anyhow = ["dep:anyhow", "error-send"]
userdata-wrappers = [] userdata-wrappers = ["parking_lot/send_guard"]
# deprecated features
serialize = ["serde"]
[dependencies] [dependencies]
mlua_derive = { version = "=0.11.0-beta.1", optional = true, path = "mlua_derive" } mlua_derive = { version = "=0.11.0", optional = true, path = "mlua_derive" }
bstr = { version = "1.0", features = ["std"], default-features = false } bstr = { version = "1.0", features = ["std"], default-features = false }
either = "1.0" either = "1.0"
num-traits = { version = "0.2.14" } num-traits = { version = "0.2.14" }
@@ -57,26 +61,26 @@ erased-serde = { version = "0.4", optional = true }
serde-value = { version = "0.7", optional = true } serde-value = { version = "0.7", optional = true }
parking_lot = { version = "0.12", features = ["arc_lock"] } parking_lot = { version = "0.12", features = ["arc_lock"] }
anyhow = { version = "1.0", optional = true } anyhow = { version = "1.0", optional = true }
rustversion = "1.0" libc = "0.2"
ffi = { package = "mlua-sys", version = "0.7.0", path = "mlua-sys" } ffi = { package = "mlua-sys", version = "0.10.0", path = "mlua-sys" }
[dev-dependencies] [dev-dependencies]
trybuild = "1.0" trybuild = "1.0"
hyper = { version = "1.2", features = ["full"] }
hyper-util = { version = "0.1.3", features = ["full"] }
http-body-util = "0.1.1"
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1.0", features = ["macros", "rt", "time"] } tokio = { version = "1.0", features = ["macros", "rt", "time"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["arbitrary_precision"] } serde_json = { version = "1.0", features = ["arbitrary_precision"] }
maplit = "1.0" maplit = "1.0"
tempfile = "3"
static_assertions = "1.0" static_assertions = "1.0"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] [target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
criterion = { version = "0.5", features = ["async_tokio"] } hyper = { version = "1.2", features = ["full"] }
rustyline = "15.0" hyper-util = { version = "0.1.3", features = ["full"] }
http-body-util = "0.1.1"
reqwest = { version = "0.12", features = ["json"] }
tempfile = "3"
criterion = { version = "0.7", features = ["async_tokio"] }
rustyline = "17.0"
tokio = { version = "1.0", features = ["full"] } tokio = { version = "1.0", features = ["full"] }
[lints.rust] [lints.rust]
@@ -90,7 +94,7 @@ required-features = ["async"]
[[bench]] [[bench]]
name = "serde" name = "serde"
harness = false harness = false
required-features = ["serialize"] required-features = ["serde"]
[[example]] [[example]]
name = "async_http_client" name = "async_http_client"
@@ -98,7 +102,7 @@ required-features = ["async", "macros"]
[[example]] [[example]]
name = "async_http_reqwest" name = "async_http_reqwest"
required-features = ["async", "serialize", "macros"] required-features = ["async", "serde", "macros"]
[[example]] [[example]]
name = "async_http_server" name = "async_http_server"
@@ -113,8 +117,8 @@ name = "guided_tour"
required-features = ["macros"] required-features = ["macros"]
[[example]] [[example]]
name = "serialize" name = "serde"
required-features = ["serialize"] required-features = ["serde"]
[[example]] [[example]]
name = "userdata" name = "userdata"
+44 -48
View File
@@ -17,20 +17,14 @@
[Benchmarks]: https://github.com/khvzak/script-bench-rs [Benchmarks]: https://github.com/khvzak/script-bench-rs
[FAQ]: FAQ.md [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`. `mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal of providing a
_safe_ (as much as possible), high level, easy to use, practical and flexible API.
> **Note** Started as an `rlua` fork, `mlua` supports Lua 5.5, 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows writing native Lua modules in Rust as well as using Lua in a standalone mode.
>
> See v0.10 [release notes](https://github.com/mlua-rs/mlua/blob/main/docs/release_notes/v0.10.md).
`mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide `mlua` is tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platforms and cross-compilation to `aarch64` (other targets are also supported).
_safe_ (as far as it's possible), high level, easy to use, practical and flexible API.
Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows to write native Lua modules in Rust as well as use Lua in a standalone mode. WebAssembly (WASM) is supported through the `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT.
`mlua` tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platform and cross-compilation to `aarch64` (other targets are also supported).
WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua/Luau versions excluding JIT.
[GitHub Actions]: https://github.com/mlua-rs/mlua/actions [GitHub Actions]: https://github.com/mlua-rs/mlua/actions
[Luau]: https://luau.org [Luau]: https://luau.org
@@ -39,9 +33,10 @@ WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for a
### Feature flags ### Feature flags
`mlua` uses feature flags to reduce the amount of dependencies, compiled code and allow to choose only required set of features. `mlua` uses feature flags to reduce the number of dependencies and compiled code, and allow choosing only the required set of features.
Below is a list of the available feature flags. By default `mlua` does not enable any features. Below is a list of the available feature flags. By default `mlua` does not enable any features.
* `lua55`: enable Lua [5.5] support
* `lua54`: enable Lua [5.4] support * `lua54`: enable Lua [5.4] support
* `lua53`: enable Lua [5.3] support * `lua53`: enable Lua [5.3] support
* `lua52`: enable Lua [5.2] support * `lua52`: enable Lua [5.2] support
@@ -51,16 +46,17 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
* `luau`: enable [Luau] support (auto vendored mode) * `luau`: enable [Luau] support (auto vendored mode)
* `luau-jit`: enable [Luau] support with JIT backend. * `luau-jit`: enable [Luau] support with JIT backend.
* `luau-vector4`: enable [Luau] support with 4-dimensional vector. * `luau-vector4`: enable [Luau] support with 4-dimensional vector.
* `vendored`: build static Lua(JIT) library from sources during `mlua` compilation using [lua-src] or [luajit-src] crates * `vendored`: build static Lua(JIT) libraries from sources during `mlua` compilation using [lua-src] or [luajit-src]
* `module`: enable module mode (building loadable `cdylib` library for Lua) * `module`: enable module mode (building loadable `cdylib` library for Lua)
* `async`: enable async/await support (any executor can be used, eg. [tokio] or [async-std]) * `async`: enable async/await support (any executor can be used, eg. [tokio] or [async-std])
* `send`: make `mlua::Lua: Send + Sync` (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`) * `send`: make `mlua::Lua: Send + Sync` (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`)
* `error-send`: make `mlua:Error: Send + Sync` * `error-send`: make `mlua:Error: Send + Sync`
* `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework * `serde`: add serialization and deserialization support to `mlua` types using [serde]
* `macros`: enable procedural macros (such as `chunk!`) * `macros`: enable procedural macros (such as `chunk!`)
* `anyhow`: enable `anyhow::Error` conversion into Lua * `anyhow`: enable `anyhow::Error` conversion into Lua
* `userdata-wrappers`: opt into `impl UserData` for `Rc<T>`/`Arc<T>`/`Rc<RefCell<T>>`/`Arc<Mutex<T>>` where `T: UserData` * `userdata-wrappers`: opt into `impl UserData` for `Rc<T>`/`Arc<T>`/`Rc<RefCell<T>>`/`Arc<Mutex<T>>` where `T: UserData`
[5.5]: https://www.lua.org/manual/5.5/manual.html
[5.4]: https://www.lua.org/manual/5.4/manual.html [5.4]: https://www.lua.org/manual/5.4/manual.html
[5.3]: https://www.lua.org/manual/5.3/manual.html [5.3]: https://www.lua.org/manual/5.3/manual.html
[5.2]: https://www.lua.org/manual/5.2/manual.html [5.2]: https://www.lua.org/manual/5.2/manual.html
@@ -78,7 +74,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
`mlua` supports async/await for all Lua versions including Luau. `mlua` supports async/await for all Lua versions including Luau.
This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6) and require running [Thread](https://docs.rs/mlua/latest/mlua/struct.Thread.html) along with enabling `feature = "async"` in `Cargo.toml`. This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6) and requires running [Thread](https://docs.rs/mlua/latest/mlua/struct.Thread.html) along with enabling `feature = "async"` in `Cargo.toml`.
**Examples**: **Examples**:
- [HTTP Client](examples/async_http_client.rs) - [HTTP Client](examples/async_http_client.rs)
@@ -93,18 +89,18 @@ This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6
cargo run --example async_http_client --features=lua54,async,macros cargo run --example async_http_client --features=lua54,async,macros
# async http client (reqwest) # async http client (reqwest)
cargo run --example async_http_reqwest --features=lua54,async,macros,serialize cargo run --example async_http_reqwest --features=lua54,async,macros,serde
# async http server # async http server
cargo run --example async_http_server --features=lua54,async,macros,send cargo run --example async_http_server --features=lua54,async,macros,send
curl -v http://localhost:3000 curl -v http://localhost:3000
``` ```
### Serialization (serde) support ### Serde support
With `serialize` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition `mlua` provides [`serde::Serialize`] trait implementation for it (including `UserData` support). With the `serde` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition, `mlua` provides the [`serde::Serialize`] trait implementation for `mlua::Value` (including `UserData` support).
[Example](examples/serialize.rs) [Example](examples/serde.rs)
[`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
[`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
@@ -114,28 +110,28 @@ With `serialize` feature flag enabled, `mlua` allows you to serialize/deserializ
You have to enable one of the features: `lua54`, `lua53`, `lua52`, `lua51`, `luajit(52)` or `luau`, according to the chosen Lua version. You have to enable one of the features: `lua54`, `lua53`, `lua52`, `lua51`, `luajit(52)` or `luau`, according to the chosen Lua version.
By default `mlua` uses `pkg-config` tool to find lua includes and libraries for the chosen Lua version. By default `mlua` uses `pkg-config` to find Lua includes and libraries for the chosen Lua version.
In most cases it works as desired, although sometimes could be more preferable to use a custom lua library. In most cases it works as desired, although sometimes it may be preferable to use a custom Lua library.
To achieve this, mlua supports `LUA_LIB`, `LUA_LIB_NAME` and `LUA_LINK` environment variables. To achieve this, mlua supports the `LUA_LIB`, `LUA_LIB_NAME` and `LUA_LINK` environment variables.
`LUA_LINK` is optional and may be `dylib` (a dynamic library) or `static` (a static library, `.a` archive). `LUA_LINK` is optional and may be `dylib` (a dynamic library) or `static` (a static library, `.a` archive).
An example how to use them: An example of how to use them:
``` sh ``` sh
my_project $ LUA_LIB=$HOME/tmp/lua-5.2.4/src LUA_LIB_NAME=lua LUA_LINK=static cargo build my_project $ LUA_LIB=$HOME/tmp/lua-5.2.4/src LUA_LIB_NAME=lua LUA_LINK=static cargo build
``` ```
`mlua` also supports vendored lua/luajit using the auxiliary crates [lua-src](https://crates.io/crates/lua-src) and `mlua` also supports vendored Lua/LuaJIT using the auxiliary crates [lua-src](https://crates.io/crates/lua-src) and
[luajit-src](https://crates.io/crates/luajit-src). [luajit-src](https://crates.io/crates/luajit-src).
Just enable the `vendored` feature and cargo will automatically build and link specified lua/luajit version. This is the easiest way to get started with `mlua`. Just enable the `vendored` feature and cargo will automatically build and link the specified Lua/LuaJIT version. This is the easiest way to get started with `mlua`.
### Standalone mode ### Standalone mode
In a standalone mode `mlua` allows to add to your application scripting support with a gently configured Lua runtime to ensure safety and soundness. In standalone mode, `mlua` allows adding scripting support to your application with a gently configured Lua runtime to ensure safety and soundness.
Add to `Cargo.toml` : Add to `Cargo.toml`:
``` toml ``` toml
[dependencies] [dependencies]
mlua = { version = "0.10", features = ["lua54", "vendored"] } mlua = { version = "0.11", features = ["lua54", "vendored"] }
``` ```
`main.rs` `main.rs`
@@ -159,21 +155,21 @@ fn main() -> LuaResult<()> {
``` ```
### Module mode ### Module mode
In a module mode `mlua` allows to create a compiled Lua module that can be loaded from Lua code using [`require`](https://www.lua.org/manual/5.4/manual.html#pdf-require). In this case `mlua` uses an external Lua runtime which could lead to potential unsafety due to unpredictability of the Lua environment and usage of libraries such as [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10). In module mode, `mlua` allows creating a compiled Lua module that can be loaded from Lua code using [`require`](https://www.lua.org/manual/5.4/manual.html#pdf-require). In this case `mlua` uses an external Lua runtime which could lead to potential unsafety due to the unpredictability of the Lua environment and usage of libraries such as [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10).
[Example](examples/module) [Example](examples/module)
Add to `Cargo.toml` : Add to `Cargo.toml`:
``` toml ``` toml
[lib] [lib]
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
mlua = { version = "0.10", features = ["lua54", "module"] } mlua = { version = "0.11", features = ["lua54", "module"] }
``` ```
`lib.rs` : `lib.rs`:
``` rust ``` rust
use mlua::prelude::*; use mlua::prelude::*;
@@ -216,14 +212,14 @@ rustflags = [
``` ```
On Linux you can build modules normally with `cargo build --release`. On Linux you can build modules normally with `cargo build --release`.
On Windows the target module will be linked with `lua5x.dll` library (depending on your feature flags). On Windows the target module will be linked with the `lua5x.dll` library (depending on your feature flags).
Your main application should provide this library. Your main application should provide this library.
Module builds don't require Lua lib or headers to be installed on the system. Module builds don't require Lua binaries or headers to be installed on the system.
### Publishing to luarocks.org ### Publishing to luarocks.org
There is a LuaRocks build backend for mlua modules [`luarocks-build-rust-mlua`]. There is a LuaRocks build backend for mlua modules: [`luarocks-build-rust-mlua`].
Modules written in Rust and published to luarocks: Modules written in Rust and published to luarocks:
- [`decasify`](https://github.com/alerque/decasify) - [`decasify`](https://github.com/alerque/decasify)
@@ -236,10 +232,10 @@ Modules written in Rust and published to luarocks:
## Safety ## Safety
One of the `mlua` goals is to provide *safe* API between Rust and Lua. One of `mlua`'s goals is to provide a *safe* API between Rust and Lua.
Every place where the Lua C API may trigger an error longjmp in any way is protected by `lua_pcall`, Every place where the Lua C API may trigger an error longjmp is protected by `lua_pcall`,
and the user of the library is protected from directly interacting with unsafe things like the Lua stack, and the user of the library is protected from directly interacting with unsafe things like the Lua stack.
and there is overhead associated with this safety. There is overhead associated with this safety.
Unfortunately, `mlua` does not provide absolute safety even without using `unsafe` . Unfortunately, `mlua` does not provide absolute safety even without using `unsafe` .
This library contains a huge amount of unsafe code. There are almost certainly bugs still lurking in this library! This library contains a huge amount of unsafe code. There are almost certainly bugs still lurking in this library!
@@ -247,8 +243,8 @@ It is surprisingly, fiendishly difficult to use the Lua C API without the potent
## Panic handling ## Panic handling
`mlua` wraps panics that are generated inside Rust callbacks in a regular Lua error. Panics could be `mlua` wraps panics that are generated inside Rust callbacks in a regular Lua error. Panics can then be
resumed then by returning or propagating the Lua error to Rust code. resumed by returning or propagating the Lua error to Rust code.
For example: For example:
``` rust ``` rust
@@ -267,16 +263,16 @@ let _ = lua.load(r#"
unreachable!() unreachable!()
``` ```
Optionally `mlua` can disable Rust panics catching in Lua via `pcall`/`xpcall` and automatically resume Optionally, `mlua` can disable Rust panic catching in Lua via `pcall`/`xpcall` and automatically resume
them across the Lua API boundary. This is controlled via `LuaOptions` and done by wrapping the Lua `pcall`/`xpcall` them across the Lua API boundary. This is controlled via `LuaOptions` and done by wrapping the Lua `pcall`/`xpcall`
functions on a way to prevent catching errors that are wrapped Rust panics. functions to prevent catching errors that are wrapped Rust panics.
`mlua` should also be panic safe in another way as well, which is that any `Lua` instances or handles `mlua` should also be panic safe in another way as well, which is that any `Lua` instances or handles
remains usable after a user generated panic, and such panics should not break internal invariants or remain usable after a user generated panic, and such panics should not break internal invariants or
leak Lua stack space. This is mostly important to safely use `mlua` types in Drop impls, as you should not be leak Lua stack space. This is mostly important to safely use `mlua` types in Drop impls, as you should not be
using panics for general error handling. using panics for general error handling.
Below is a list of `mlua` behaviors that should be considered a bug. Below is a list of `mlua` behaviors that should be considered bugs.
If you encounter them, a bug report would be very welcome: If you encounter them, a bug report would be very welcome:
+ If you can cause UB with `mlua` without typing the word "unsafe", this is a bug. + If you can cause UB with `mlua` without typing the word "unsafe", this is a bug.
@@ -289,12 +285,12 @@ If you encounter them, a bug report would be very welcome:
## Sandboxing ## Sandboxing
Please check the [Luau Sandboxing] page if you are interested in running untrusted Lua scripts in controlled environment. Please check the [Luau Sandboxing] page if you are interested in running untrusted Lua scripts in a controlled environment.
`mlua` provides `Lua::sandbox` method for enabling sandbox mode (Luau only). `mlua` provides the `Lua::sandbox` method for enabling sandbox mode (Luau only).
[Luau Sandboxing]: https://luau.org/sandbox [Luau Sandboxing]: https://luau.org/sandbox
## License ## License
This project is licensed under the [MIT license](LICENSE) This project is licensed under the [MIT license](LICENSE).
+55 -1
View File
@@ -1,7 +1,7 @@
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration; use std::time::Duration;
use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use tokio::task; use tokio::task;
@@ -128,6 +128,22 @@ fn table_traversal_sequence(c: &mut Criterion) {
}); });
} }
fn table_ref_clone(c: &mut Criterion) {
let lua = Lua::new();
let t = lua.create_table().unwrap();
c.bench_function("table [ref clone]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
let _t2 = t.clone();
},
BatchSize::SmallInput,
);
});
}
fn function_create(c: &mut Criterion) { fn function_create(c: &mut Criterion) {
let lua = Lua::new(); let lua = Lua::new();
@@ -350,6 +366,42 @@ fn userdata_call_method(c: &mut Criterion) {
}); });
} }
// A userdata method call that goes through an implicit `__index` function
fn userdata_call_method_complex(c: &mut Criterion) {
struct UserData(u64);
impl LuaUserData for UserData {
fn register(registry: &mut LuaUserDataRegistry<Self>) {
registry.add_field_method_get("val", |_, this| Ok(this.0));
registry.add_method_mut("inc_by", |_, this, by: u64| {
this.0 += by;
Ok(this.0)
});
#[cfg(feature = "luau")]
registry.enable_namecall();
}
}
let lua = Lua::new();
let ud = lua.create_userdata(UserData(0)).unwrap();
let inc_by = lua
.load("function(ud, s) return ud:inc_by(s) end")
.eval::<LuaFunction>()
.unwrap();
c.bench_function("userdata [call method complex]", |b| {
b.iter_batched(
|| {
collect_gc_twice(&lua);
},
|_| {
inc_by.call::<()>((&ud, 1)).unwrap();
},
BatchSize::SmallInput,
);
});
}
fn userdata_async_call_method(c: &mut Criterion) { fn userdata_async_call_method(c: &mut Criterion) {
struct UserData(i64); struct UserData(i64);
impl LuaUserData for UserData { impl LuaUserData for UserData {
@@ -399,6 +451,7 @@ criterion_group! {
table_traversal_pairs, table_traversal_pairs,
table_traversal_for_each, table_traversal_for_each,
table_traversal_sequence, table_traversal_sequence,
table_ref_clone,
function_create, function_create,
function_call_sum, function_call_sum,
@@ -413,6 +466,7 @@ criterion_group! {
userdata_create, userdata_create,
userdata_call_index, userdata_call_index,
userdata_call_method, userdata_call_method,
userdata_call_method_complex,
userdata_async_call_method, userdata_async_call_method,
} }
+1 -1
View File
@@ -1,6 +1,6 @@
use std::time::Duration; use std::time::Duration;
use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
use mlua::prelude::*; use mlua::prelude::*;
+1 -1
View File
@@ -336,7 +336,7 @@ In previous mlua versions, building a Lua module for Windows requires having Lua
In contrast, on Linux and macOS, modules can be built without any external dependencies using the `-undefined=dynamic_lookup` linker flag. In contrast, on Linux and macOS, modules can be built without any external dependencies using the `-undefined=dynamic_lookup` linker flag.
With Rust 1.71+ it's now possible to lift this restriction for Windows as well. You can build modules normally and they will be linked with With Rust 1.71+ it's now possible to lift this restriction for Windows as well. You can build modules normally and they will be linked with
`lua54.dll`/`lua53.dll`/`lua52.dll`/`lua51.dll` depending on the enabled Lua version. `lua5x.dll` depending on the enabled Lua version.
You still need to have the dll although, linked to application where the module will be loaded. You still need to have the dll although, linked to application where the module will be loaded.
+1 -1
View File
@@ -5,7 +5,7 @@ use hyper::body::Incoming;
use hyper_util::client::legacy::Client as HyperClient; use hyper_util::client::legacy::Client as HyperClient;
use hyper_util::rt::TokioExecutor; use hyper_util::rt::TokioExecutor;
use mlua::{chunk, ExternalResult, Lua, Result, UserData, UserDataMethods}; use mlua::{ExternalResult, Lua, Result, UserData, UserDataMethods, chunk};
struct BodyReader(Incoming); struct BodyReader(Incoming);
+1 -1
View File
@@ -1,4 +1,4 @@
use mlua::{chunk, ExternalResult, Lua, LuaSerdeExt, Result, Value}; use mlua::{ExternalResult, Lua, LuaSerdeExt, Result, Value, chunk};
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> { async fn main() -> Result<()> {
+1 -1
View File
@@ -11,7 +11,7 @@ use hyper::{Request, Response};
use hyper_util::rt::TokioIo; use hyper_util::rt::TokioIo;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use mlua::{chunk, Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods}; use mlua::{Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods, chunk};
/// Wrapper around incoming request that implements UserData /// Wrapper around incoming request that implements UserData
struct LuaRequest(SocketAddr, Request<Incoming>); struct LuaRequest(SocketAddr, Request<Incoming>);
+1 -1
View File
@@ -4,7 +4,7 @@ use std::net::SocketAddr;
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream}; use tokio::net::{TcpListener, TcpStream};
use mlua::{chunk, BString, Function, Lua, UserData, UserDataMethods}; use mlua::{BString, Function, Lua, UserData, UserDataMethods, chunk};
struct LuaTcpStream(TcpStream); struct LuaTcpStream(TcpStream);
+1 -1
View File
@@ -1,7 +1,7 @@
use std::f32; use std::f32;
use std::iter::FromIterator; use std::iter::FromIterator;
use mlua::{chunk, FromLua, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Value, Variadic}; use mlua::{FromLua, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Value, Variadic, chunk};
fn main() -> Result<()> { fn main() -> Result<()> {
// You can create a new Lua state with `Lua::new()`. This loads the default Lua std library // You can create a new Lua state with `Lua::new()`. This loads the default Lua std library
+1
View File
@@ -10,6 +10,7 @@ crate-type = ["cdylib"]
[workspace] [workspace]
[features] [features]
lua55 = ["mlua/lua55"]
lua54 = ["mlua/lua54"] lua54 = ["mlua/lua54"]
lua53 = ["mlua/lua53"] lua53 = ["mlua/lua53"]
lua52 = ["mlua/lua52"] lua52 = ["mlua/lua52"]
+10 -8
View File
@@ -20,14 +20,16 @@ fn main() {
match lua.load(&line).eval::<MultiValue>() { match lua.load(&line).eval::<MultiValue>() {
Ok(values) => { Ok(values) => {
editor.add_history_entry(line).unwrap(); editor.add_history_entry(line).unwrap();
println!( if values.len() > 0 {
"{}", println!(
values "{}",
.iter() values
.map(|value| format!("{:#?}", value)) .iter()
.collect::<Vec<_>>() .map(|value| format!("{:#?}", value))
.join("\t") .collect::<Vec<_>>()
); .join("\t")
);
}
break; break;
} }
Err(Error::SyntaxError { Err(Error::SyntaxError {
+1 -1
View File
@@ -1,4 +1,4 @@
use mlua::{chunk, Lua, MetaMethod, Result, UserData}; use mlua::{Lua, MetaMethod, Result, UserData, chunk};
#[derive(Default)] #[derive(Default)]
struct Rectangle { struct Rectangle {
+10 -7
View File
@@ -1,9 +1,9 @@
[package] [package]
name = "mlua-sys" name = "mlua-sys"
version = "0.7.0" version = "0.10.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"] authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.71" rust-version = "1.88"
edition = "2021" edition = "2024"
repository = "https://github.com/mlua-rs/mlua" repository = "https://github.com/mlua-rs/mlua"
documentation = "https://docs.rs/mlua-sys" documentation = "https://docs.rs/mlua-sys"
readme = "README.md" readme = "README.md"
@@ -12,14 +12,15 @@ license = "MIT"
links = "lua" links = "lua"
build = "build/main.rs" build = "build/main.rs"
description = """ description = """
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau Low level (FFI) bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including LuaJIT) and Luau
""" """
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["lua54", "vendored"] features = ["lua55", "vendored"]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[features] [features]
lua55 = []
lua54 = [] lua54 = []
lua53 = [] lua53 = []
lua52 = [] lua52 = []
@@ -30,17 +31,19 @@ luau = ["luau0-src"]
luau-codegen = ["luau"] luau-codegen = ["luau"]
luau-vector4 = ["luau"] luau-vector4 = ["luau"]
vendored = ["lua-src", "luajit-src"] vendored = ["lua-src", "luajit-src"]
external = []
module = [] module = []
[dependencies] [dependencies]
libc = "0.2"
[build-dependencies] [build-dependencies]
cc = "1.0" cc = "1.0"
cfg-if = "1.0" cfg-if = "1.0"
pkg-config = "0.3.17" pkg-config = "0.3.17"
lua-src = { version = ">= 547.1.0, < 547.2.0", optional = true } lua-src = { version = ">= 550.0.0, < 550.1.0", optional = true }
luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true } luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true }
luau0-src = { version = "0.14.2", optional = true } luau0-src = { version = "0.18.0", optional = true }
[lints.rust] [lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] } unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
+2 -1
View File
@@ -1,8 +1,9 @@
# mlua-sys # mlua-sys
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and [Luau]. Low level (FFI) bindings to Lua 5.5/5.4/5.3/5.2/5.1 (including [LuaJIT]) and [Luau].
Intended to be consumed by the [mlua] crate. Intended to be consumed by the [mlua] crate.
[LuaJIT]: https://github.com/LuaJIT/LuaJIT
[Luau]: https://github.com/luau-lang/luau [Luau]: https://github.com/luau-lang/luau
[mlua]: https://crates.io/crates/mlua [mlua]: https://crates.io/crates/mlua
+7 -11
View File
@@ -31,18 +31,16 @@ pub fn probe_lua() {
// Find using `pkg-config` // Find using `pkg-config`
#[cfg(feature = "lua55")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.5", "5.6", ["lua5.5", "lua-5.5", "lua55"], "5.5");
#[cfg(feature = "lua54")] #[cfg(feature = "lua54")]
let (incl_bound, excl_bound, alt_probe, ver) = let (incl_bound, excl_bound, alt_probe, ver) = ("5.4", "5.5", ["lua5.4", "lua-5.4", "lua54"], "5.4");
("5.4", "5.5", ["lua5.4", "lua-5.4", "lua54"], "5.4");
#[cfg(feature = "lua53")] #[cfg(feature = "lua53")]
let (incl_bound, excl_bound, alt_probe, ver) = let (incl_bound, excl_bound, alt_probe, ver) = ("5.3", "5.4", ["lua5.3", "lua-5.3", "lua53"], "5.3");
("5.3", "5.4", ["lua5.3", "lua-5.3", "lua53"], "5.3");
#[cfg(feature = "lua52")] #[cfg(feature = "lua52")]
let (incl_bound, excl_bound, alt_probe, ver) = let (incl_bound, excl_bound, alt_probe, ver) = ("5.2", "5.3", ["lua5.2", "lua-5.2", "lua52"], "5.2");
("5.2", "5.3", ["lua5.2", "lua-5.2", "lua52"], "5.2");
#[cfg(feature = "lua51")] #[cfg(feature = "lua51")]
let (incl_bound, excl_bound, alt_probe, ver) = let (incl_bound, excl_bound, alt_probe, ver) = ("5.1", "5.2", ["lua5.1", "lua-5.1", "lua51"], "5.1");
("5.1", "5.2", ["lua5.1", "lua-5.1", "lua51"], "5.1");
#[cfg(feature = "luajit")] #[cfg(feature = "luajit")]
let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", [], "JIT"); let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", [], "JIT");
@@ -54,9 +52,7 @@ pub fn probe_lua() {
if lua.is_err() { if lua.is_err() {
for pkg in alt_probe { for pkg in alt_probe {
lua = pkg_config::Config::new() lua = pkg_config::Config::new().cargo_metadata(true).probe(pkg);
.cargo_metadata(true)
.probe(pkg);
if lua.is_ok() { if lua.is_ok() {
break; break;
+3
View File
@@ -1,6 +1,9 @@
#![allow(dead_code)] #![allow(dead_code)]
pub fn probe_lua() { pub fn probe_lua() {
#[cfg(feature = "lua55")]
let artifacts = lua_src::Build::new().build(lua_src::Lua55);
#[cfg(feature = "lua54")] #[cfg(feature = "lua54")]
let artifacts = lua_src::Build::new().build(lua_src::Lua54); let artifacts = lua_src::Build::new().build(lua_src::Lua54);
+9 -7
View File
@@ -1,19 +1,21 @@
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(all(feature = "lua54", not(any(feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] { if #[cfg(all(feature = "lua55", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs"); include!("main_inner.rs");
} else if #[cfg(all(feature = "lua53", not(any(feature = "lua54", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] { } else if #[cfg(all(feature = "lua54", not(any(feature = "lua55", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs"); include!("main_inner.rs");
} else if #[cfg(all(feature = "lua52", not(any(feature = "lua54", feature = "lua53", feature = "lua51", feature = "luajit", feature = "luau"))))] { } else if #[cfg(all(feature = "lua53", not(any(feature = "lua55", feature = "lua54", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs"); include!("main_inner.rs");
} else if #[cfg(all(feature = "lua51", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit", feature = "luau"))))] { } else if #[cfg(all(feature = "lua52", not(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs"); include!("main_inner.rs");
} else if #[cfg(all(feature = "luajit", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luau"))))] { } else if #[cfg(all(feature = "lua51", not(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs"); include!("main_inner.rs");
} else if #[cfg(all(feature = "luau", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit"))))] { } else if #[cfg(all(feature = "luajit", not(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "luau", not(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit"))))] {
include!("main_inner.rs"); include!("main_inner.rs");
} else { } else {
fn main() { fn main() {
compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"); compile_error!("You can enable only one of the features: lua55, lua54, lua53, lua52, lua51, luajit, luajit52, luau");
} }
} }
} }
+19 -12
View File
@@ -14,22 +14,29 @@ fn main() {
#[cfg(all(feature = "luau", feature = "module", windows))] #[cfg(all(feature = "luau", feature = "module", windows))]
compile_error!("Luau does not support `module` mode on Windows"); compile_error!("Luau does not support `module` mode on Windows");
#[cfg(all(feature = "module", feature = "vendored"))] #[cfg(any(
compile_error!("`vendored` and `module` features are mutually exclusive"); all(feature = "vendored", any(feature = "external", feature = "module")),
all(feature = "external", any(feature = "vendored", feature = "module")),
all(feature = "module", any(feature = "vendored", feature = "external"))
))]
compile_error!("`vendored`, `external` and `module` features are mutually exclusive");
println!("cargo:rerun-if-changed=build"); println!("cargo:rerun-if-changed=build");
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); // Check if compilation and linking is handled by external crate
if target_os == "windows" && cfg!(feature = "module") { if cfg!(not(feature = "external")) {
if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() { let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
// Don't use raw-dylib linking if target_os == "windows" && cfg!(feature = "module") {
find::probe_lua(); if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() {
return; // Don't use raw-dylib linking
find::probe_lua();
return;
}
println!("cargo:rustc-cfg=raw_dylib");
} }
println!("cargo:rustc-cfg=raw_dylib"); #[cfg(not(feature = "module"))]
find::probe_lua();
} }
#[cfg(not(feature = "module"))]
find::probe_lua();
} }
+31 -22
View File
@@ -1,4 +1,4 @@
//! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau. //! Low level bindings to Lua 5.5/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)]
#![allow(clippy::missing_safety_doc)] #![allow(clippy::missing_safety_doc)]
@@ -8,6 +8,9 @@
use std::os::raw::c_int; use std::os::raw::c_int;
#[cfg(any(feature = "lua55", doc))]
pub use lua55::*;
#[cfg(any(feature = "lua54", doc))] #[cfg(any(feature = "lua54", doc))]
pub use lua54::*; pub use lua54::*;
@@ -23,7 +26,7 @@ pub use lua51::*;
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
pub use luau::*; pub use luau::*;
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
#[doc(hidden)] #[doc(hidden)]
pub const LUA_MAX_UPVALUES: c_int = 255; pub const LUA_MAX_UPVALUES: c_int = 255;
@@ -40,14 +43,22 @@ pub const LUA_MAX_UPVALUES: c_int = 200;
#[doc(hidden)] #[doc(hidden)]
pub const LUA_TRACEBACK_STACK: c_int = 11; pub const LUA_TRACEBACK_STACK: c_int = 11;
// Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/common/alloc.rs // The minimum alignment guaranteed by the architecture.
// The minimum alignment guaranteed by the architecture. This value is used to // Copied from https://github.com/rust-lang/rust/blob/main/library/std/src/sys/alloc/mod.rs
// add fast paths for low alignment values. #[doc(hidden)]
#[cfg(any( #[rustfmt::skip]
pub const SYS_MIN_ALIGN: usize = if cfg!(any(
all(target_arch = "riscv32", any(target_os = "espidf", target_os = "zkvm")),
all(target_arch = "xtensa", target_os = "espidf"),
)) {
// The allocator on the esp-idf and zkvm platforms guarantees 4 byte alignment.
4
} else if cfg!(any(
target_arch = "x86", target_arch = "x86",
target_arch = "arm", target_arch = "arm",
target_arch = "m68k", target_arch = "m68k",
target_arch = "csky", target_arch = "csky",
target_arch = "loongarch32",
target_arch = "mips", target_arch = "mips",
target_arch = "mips32r6", target_arch = "mips32r6",
target_arch = "powerpc", target_arch = "powerpc",
@@ -55,12 +66,11 @@ pub const LUA_TRACEBACK_STACK: c_int = 11;
target_arch = "sparc", target_arch = "sparc",
target_arch = "wasm32", target_arch = "wasm32",
target_arch = "hexagon", target_arch = "hexagon",
all(target_arch = "riscv32", not(any(target_os = "espidf", target_os = "zkvm"))), target_arch = "riscv32",
all(target_arch = "xtensa", not(target_os = "espidf")), target_arch = "xtensa",
))] )) {
#[doc(hidden)] 8
pub const SYS_MIN_ALIGN: usize = 8; } else if cfg!(any(
#[cfg(any(
target_arch = "x86_64", target_arch = "x86_64",
target_arch = "aarch64", target_arch = "aarch64",
target_arch = "arm64ec", target_arch = "arm64ec",
@@ -71,20 +81,19 @@ pub const SYS_MIN_ALIGN: usize = 8;
target_arch = "sparc64", target_arch = "sparc64",
target_arch = "riscv64", target_arch = "riscv64",
target_arch = "wasm64", target_arch = "wasm64",
))] )) {
#[doc(hidden)] 16
pub const SYS_MIN_ALIGN: usize = 16; } else {
// The allocator on the esp-idf and zkvm platforms guarantee 4 byte alignment. panic!("no value for SYS_MIN_ALIGN")
#[cfg(any( };
all(target_arch = "riscv32", any(target_os = "espidf", target_os = "zkvm")),
all(target_arch = "xtensa", target_os = "espidf"),
))]
#[doc(hidden)]
pub const SYS_MIN_ALIGN: usize = 4;
#[macro_use] #[macro_use]
mod macros; mod macros;
#[cfg(any(feature = "lua55", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua55")))]
pub mod lua55;
#[cfg(any(feature = "lua54", doc))] #[cfg(any(feature = "lua54", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] #[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub mod lua54; pub mod lua54;
+53 -49
View File
@@ -2,6 +2,7 @@
//! //!
//! Based on github.com/keplerproject/lua-compat-5.3 //! Based on github.com/keplerproject/lua-compat-5.3
use std::ffi::CStr;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::{mem, ptr}; use std::{mem, ptr};
@@ -20,8 +21,8 @@ unsafe fn compat53_reverse(L: *mut lua_State, mut a: c_int, mut b: c_int) {
} }
} }
const COMPAT53_LEVELS1: c_int = 12; // size of the first part of the stack const COMPAT53_LEVELS1: c_int = 10; // size of the first part of the stack
const COMPAT53_LEVELS2: c_int = 10; // size of the second part of the stack const COMPAT53_LEVELS2: c_int = 11; // size of the second part of the stack
unsafe fn compat53_countlevels(L: *mut lua_State) -> c_int { unsafe fn compat53_countlevels(L: *mut lua_State) -> c_int {
let mut ar: lua_Debug = mem::zeroed(); let mut ar: lua_Debug = mem::zeroed();
@@ -54,11 +55,7 @@ unsafe fn compat53_checkmode(
while *st != 0 && *st != c { while *st != 0 && *st != c {
st = st.offset(1); st = st.offset(1);
} }
if *st == c { if *st == c { st } else { ptr::null() }
st
} else {
ptr::null()
}
} }
if !mode.is_null() && strchr(mode, *modename).is_null() { if !mode.is_null() && strchr(mode, *modename).is_null() {
@@ -88,11 +85,10 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) ->
lua_pop(L, 1); // remove value (but keep name) lua_pop(L, 1); // remove value (but keep name)
return 1; return 1;
} else if compat53_findfield(L, objidx, level - 1) != 0 { } else if compat53_findfield(L, objidx, level - 1) != 0 {
// try recursively // stack: lib_name, lib_table, field_name (top)
lua_remove(L, -2); // remove table (but keep name) lua_pushliteral(L, c"."); // place '.' between the two names
lua_pushliteral(L, c"."); lua_replace(L, -3); // (in the slot occupied by table)
lua_insert(L, -2); // place '.' between the two names lua_concat(L, 3); // lib_name.field_name
lua_concat(L, 3);
return 1; return 1;
} }
} }
@@ -101,13 +97,20 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) ->
0 // not found 0 // not found
} }
unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, ar: *mut lua_Debug) -> c_int { unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, L1: *mut lua_State, ar: *mut lua_Debug) -> c_int {
let top = lua_gettop(L); let top = lua_gettop(L);
lua_getinfo(L, cstr!("f"), ar); // push function lua_getinfo(L1, cstr!("f"), ar); // push function
lua_xmove(L1, L, 1); // and move onto L
lua_pushvalue(L, LUA_GLOBALSINDEX); lua_pushvalue(L, LUA_GLOBALSINDEX);
luaL_checkstack(L, 6, cstr!("not enough stack")); // slots for 'findfield'
if compat53_findfield(L, top + 1, 2) != 0 { if compat53_findfield(L, top + 1, 2) != 0 {
let name = lua_tostring(L, -1);
if CStr::from_ptr(name).to_bytes().starts_with(b"_G.") {
lua_pushstring(L, name.add(3)); // push name without prefix
lua_remove(L, -2); // remove original name
}
lua_copy(L, -1, top + 1); // move name to proper place lua_copy(L, -1, top + 1); // move name to proper place
lua_pop(L, 2); // remove pushed values lua_settop(L, top + 1); // remove pushed values
1 1
} else { } else {
lua_settop(L, top); // remove function and global table lua_settop(L, top); // remove function and global table
@@ -115,27 +118,23 @@ unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, ar: *mut lua_Debug) ->
} }
} }
unsafe fn compat53_pushfuncname(L: *mut lua_State, ar: *mut lua_Debug) { unsafe fn compat53_pushfuncname(L: *mut lua_State, L1: *mut lua_State, ar: *mut lua_Debug) {
if *(*ar).namewhat != b'\0' as c_char { // try first a global name
// is there a name? if compat53_pushglobalfuncname(L, L1, ar) != 0 {
lua_pushfstring(L, cstr!("function '%s'"), (*ar).name); lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1));
lua_remove(L, -2); // remove name
} else if *(*ar).namewhat != b'\0' as c_char {
// use name from code
lua_pushfstring(L, cstr!("%s '%s'"), (*ar).namewhat, (*ar).name);
} else if *(*ar).what == b'm' as c_char { } else if *(*ar).what == b'm' as c_char {
// main? // main?
lua_pushliteral(L, c"main chunk"); lua_pushliteral(L, c"main chunk");
} else if *(*ar).what == b'C' as c_char { } else if *(*ar).what != b'C' as c_char {
if compat53_pushglobalfuncname(L, ar) != 0 { // for Lua functions, use <file:line>
lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1)); let short_src = (*ar).short_src.as_ptr();
lua_remove(L, -2); // remove name lua_pushfstring(L, cstr!("function <%s:%d>"), short_src, (*ar).linedefined);
} else {
lua_pushliteral(L, c"?");
}
} else { } else {
lua_pushfstring( lua_pushliteral(L, c"?");
L,
cstr!("function <%s:%d>"),
(*ar).short_src.as_ptr(),
(*ar).linedefined,
);
} }
} }
@@ -186,7 +185,8 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
if lua_type(L, idx) == LUA_TNUMBER { if lua_type(L, idx) == LUA_TNUMBER {
let n = lua_tonumber(L, idx); let n = lua_tonumber(L, idx);
let i = lua_tointeger(L, idx); let i = lua_tointeger(L, idx);
if (n - i as lua_Number).abs() < lua_Number::EPSILON { // Lua 5.3+ returns "false" for `-0.0`
if n.to_bits() == (i as lua_Number).to_bits() {
return 1; return 1;
} }
} }
@@ -458,32 +458,36 @@ pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const
let mut ar: lua_Debug = mem::zeroed(); let mut ar: lua_Debug = mem::zeroed();
let top = lua_gettop(L); let top = lua_gettop(L);
let numlevels = compat53_countlevels(L1); let numlevels = compat53_countlevels(L1);
let mark = if numlevels > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 { #[rustfmt::skip]
COMPAT53_LEVELS1 let mut limit = if numlevels - level > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 { COMPAT53_LEVELS1 } else { -1 };
} else {
0
};
if !msg.is_null() { if !msg.is_null() {
lua_pushfstring(L, cstr!("%s\n"), msg); lua_pushfstring(L, cstr!("%s\n"), msg);
} }
lua_pushliteral(L, c"stack traceback:"); lua_pushliteral(L, c"stack traceback:");
while lua_getstack(L1, level, &mut ar) != 0 { while lua_getstack(L1, level, &mut ar) != 0 {
level += 1; if limit == 0 {
if level == mark {
// too many levels? // too many levels?
lua_pushliteral(L, c"\n\t..."); // add a '...' let n = numlevels - level - COMPAT53_LEVELS2;
level = numlevels - COMPAT53_LEVELS2; // and skip to last ones // add warning about skip ("n + 1" because we skip current level too)
lua_pushfstring(L, cstr!("\n\t...\t(skipping %d levels)"), n + 1); // add warning about skip
level += n; // and skip to last levels
} else { } else {
lua_getinfo(L1, cstr!("Slnt"), &mut ar); lua_getinfo(L1, cstr!("Sln"), &mut ar);
lua_pushfstring(L, cstr!("\n\t%s:"), ar.short_src.as_ptr()); if *ar.what != b't' as c_char {
if ar.currentline > 0 { if ar.currentline <= 0 {
lua_pushfstring(L, cstr!("%d:"), ar.currentline); lua_pushfstring(L, cstr!("\n\t%s: in "), ar.short_src.as_ptr());
} else {
lua_pushfstring(L, cstr!("\n\t%s:%d: in "), ar.short_src.as_ptr(), ar.currentline);
}
compat53_pushfuncname(L, L1, &mut ar);
lua_concat(L, lua_gettop(L) - top);
} else {
lua_pushstring(L, cstr!("\n\t(...tail calls...)"));
} }
lua_pushliteral(L, c" in ");
compat53_pushfuncname(L, &mut ar);
lua_concat(L, lua_gettop(L) - top);
} }
level += 1;
limit -= 1;
} }
lua_concat(L, lua_gettop(L) - top); lua_concat(L, lua_gettop(L) - top);
} }
+56 -4
View File
@@ -107,8 +107,6 @@ pub unsafe fn luaL_optstring(L: *mut lua_State, n: c_int, d: *const c_char) -> *
luaL_optlstring(L, n, d, ptr::null_mut()) luaL_optlstring(L, n, d, ptr::null_mut())
} }
// Deprecated from 5.3: luaL_checkint, luaL_optint, luaL_checklong, luaL_optlong
#[inline(always)] #[inline(always)]
pub unsafe fn luaL_typename(L: *mut lua_State, i: c_int) -> *const c_char { pub unsafe fn luaL_typename(L: *mut lua_State, i: c_int) -> *const c_char {
lua::lua_typename(L, lua::lua_type(L, i)) lua::lua_typename(L, lua::lua_type(L, i))
@@ -138,8 +136,62 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) {
lua::lua_getfield_(L, lua::LUA_REGISTRYINDEX, n); lua::lua_getfield_(L, lua::LUA_REGISTRYINDEX, n);
} }
// TODO: luaL_opt #[inline(always)]
pub unsafe fn luaL_opt<T>(
L: *mut lua_State,
f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T,
n: c_int,
d: T,
) -> T {
if lua::lua_isnoneornil(L, n) != 0 {
d
} else {
f(L, n)
}
}
// //
// TODO: Generic Buffer Manipulation // Generic Buffer Manipulation
// //
#[cfg(target_arch = "wasm32")]
const BUFSIZ: usize = 1024; // WASI libc's BUFSIZ is 1024
#[cfg(not(target_arch = "wasm32"))]
const BUFSIZ: usize = libc::BUFSIZ as usize;
// The buffer size used by the lauxlib buffer system.
// The "16384" workaround is taken from the LuaJIT source code.
pub const LUAL_BUFFERSIZE: usize = if BUFSIZ > 16384 { 8192 } else { BUFSIZ };
#[repr(C)]
pub struct luaL_Buffer {
pub p: *mut c_char, // current position in buffer
pub lvl: c_int, // number of strings in the stack
pub L: *mut lua_State,
pub buffer: [c_char; LUAL_BUFFERSIZE],
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer);
pub fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char;
pub fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, l: usize);
pub fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char);
pub fn luaL_addvalue(B: *mut luaL_Buffer);
pub fn luaL_pushresult(B: *mut luaL_Buffer);
}
#[inline(always)]
pub unsafe fn luaL_addchar(B: *mut luaL_Buffer, c: c_char) {
let buffer_end = (*B).buffer.as_mut_ptr().add(LUAL_BUFFERSIZE);
if (*B).p >= buffer_end {
luaL_prepbuffer(B);
}
*(*B).p = c;
(*B).p = (*B).p.add(1);
}
#[inline(always)]
pub unsafe fn luaL_addsize(B: *mut luaL_Buffer, n: usize) {
(*B).p = (*B).p.add(n);
}
+4 -1
View File
@@ -270,7 +270,10 @@ pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) {
lua_pushcclosure(L, f, 0) lua_pushcclosure(L, f, 0)
} }
// TODO: lua_strlen #[inline(always)]
pub unsafe fn lua_strlen(L: *mut lua_State, i: c_int) -> usize {
lua_objlen(L, i)
}
#[inline(always)] #[inline(always)]
pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int { pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int {
+4 -3
View File
@@ -51,7 +51,8 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
if lua_type(L, idx) == LUA_TNUMBER { if lua_type(L, idx) == LUA_TNUMBER {
let n = lua_tonumber(L, idx); let n = lua_tonumber(L, idx);
let i = lua_tointeger(L, idx); let i = lua_tointeger(L, idx);
if (n - i as lua_Number).abs() < lua_Number::EPSILON { // Lua 5.3+ returns "false" for `-0.0`
if n.to_bits() == (i as lua_Number).to_bits() {
return 1; return 1;
} }
} }
@@ -124,7 +125,7 @@ pub unsafe fn lua_rawget(L: *mut lua_State, idx: c_int) -> c_int {
#[inline(always)] #[inline(always)]
pub unsafe fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int { pub unsafe fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int {
let n = n.try_into().expect("cannot convert index to lua_Integer"); let n = n.try_into().expect("cannot convert index to c_int");
lua_rawgeti_(L, idx, n); lua_rawgeti_(L, idx, n);
lua_type(L, -1) lua_type(L, -1)
} }
@@ -152,7 +153,7 @@ pub unsafe fn lua_seti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) {
#[inline(always)] #[inline(always)]
pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) { pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) {
let n = n.try_into().expect("cannot convert index from lua_Integer"); let n = n.try_into().expect("cannot convert index to c_int");
lua_rawseti_(L, idx, n) lua_rawseti_(L, idx, n)
} }
+67 -4
View File
@@ -32,7 +32,7 @@ unsafe extern "C-unwind" {
pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int; pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int;
pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char; pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char;
pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize) pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize)
-> *const c_char; -> *const c_char;
pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number; pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number;
pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number; pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number;
pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer; pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer;
@@ -166,13 +166,76 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) {
lua::lua_getfield_(L, lua::LUA_REGISTRYINDEX, n); lua::lua_getfield_(L, lua::LUA_REGISTRYINDEX, n);
} }
// luaL_opt would be implemented here but it is undocumented, so it's omitted
#[inline(always)] #[inline(always)]
pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int { pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int {
luaL_loadbufferx(L, s, sz, n, ptr::null()) luaL_loadbufferx(L, s, sz, n, ptr::null())
} }
#[inline(always)]
pub unsafe fn luaL_opt<T>(
L: *mut lua_State,
f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T,
n: c_int,
d: T,
) -> T {
if lua::lua_isnoneornil(L, n) != 0 {
d
} else {
f(L, n)
}
}
// //
// TODO: Generic Buffer Manipulation // Generic Buffer Manipulation
// //
#[cfg(target_arch = "wasm32")]
const BUFSIZ: usize = 1024; // WASI libc's BUFSIZ is 1024
#[cfg(not(target_arch = "wasm32"))]
const BUFSIZ: usize = libc::BUFSIZ as usize;
// The buffer size used by the lauxlib buffer system.
// The "16384" workaround is taken from the LuaJIT source code.
pub const LUAL_BUFFERSIZE: usize = if BUFSIZ > 16384 { 8192 } else { BUFSIZ };
#[repr(C)]
pub struct luaL_Buffer {
pub b: *mut c_char, // buffer address
pub size: usize, // buffer size
pub n: usize, // number of characters in buffer
pub L: *mut lua_State,
pub initb: [c_char; LUAL_BUFFERSIZE], // initial buffer space
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer);
pub fn luaL_prepbuffsize(B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
pub fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, l: usize);
pub fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char);
pub fn luaL_addvalue(B: *mut luaL_Buffer);
pub fn luaL_pushresult(B: *mut luaL_Buffer);
pub fn luaL_pushresultsize(B: *mut luaL_Buffer, sz: usize);
pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
}
// Macro implementations as inline functions
#[inline(always)]
pub unsafe fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char {
luaL_prepbuffsize(B, LUAL_BUFFERSIZE)
}
#[inline(always)]
pub unsafe fn luaL_addchar(B: *mut luaL_Buffer, c: c_char) {
if (*B).n >= (*B).size {
luaL_prepbuffsize(B, 1);
}
*(*B).b.add((*B).n) = c;
(*B).n += 1;
}
#[inline(always)]
pub unsafe fn luaL_addsize(B: *mut luaL_Buffer, n: usize) {
(*B).n += n;
}
+64 -5
View File
@@ -1,7 +1,7 @@
//! Contains definitions from `lauxlib.h`. //! Contains definitions from `lauxlib.h`.
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::ptr; use std::{mem, ptr};
use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State}; use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
@@ -31,7 +31,7 @@ unsafe extern "C-unwind" {
pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int; pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int;
pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char; pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char;
pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize) pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize)
-> *const c_char; -> *const c_char;
pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number; pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number;
pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number; pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number;
pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer; pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer;
@@ -166,13 +166,72 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
luaL_tolstring_(L, lua::lua_absindex(L, idx), len) luaL_tolstring_(L, lua::lua_absindex(L, idx), len)
} }
// luaL_opt would be implemented here but it is undocumented, so it's omitted
#[inline(always)] #[inline(always)]
pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int { pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int {
luaL_loadbufferx(L, s, sz, n, ptr::null()) luaL_loadbufferx(L, s, sz, n, ptr::null())
} }
#[inline(always)]
pub unsafe fn luaL_opt<T>(
L: *mut lua_State,
f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T,
n: c_int,
d: T,
) -> T {
if lua::lua_isnoneornil(L, n) != 0 {
d
} else {
f(L, n)
}
}
// //
// TODO: Generic Buffer Manipulation // Generic Buffer Manipulation
// //
// The buffer size used by the lauxlib buffer system.
// In Lua 5.3: LUAL_BUFFERSIZE = (int)(0x80 * sizeof(void*) * sizeof(lua_Integer))
#[rustfmt::skip]
pub const LUAL_BUFFERSIZE: usize = 0x80 * mem::size_of::<*const ()>() * mem::size_of::<lua_Integer>();
#[repr(C)]
pub struct luaL_Buffer {
pub b: *mut c_char, // buffer address
pub size: usize, // buffer size
pub n: usize, // number of characters in buffer
pub L: *mut lua_State,
pub initb: [c_char; LUAL_BUFFERSIZE], // initial buffer space
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer);
pub fn luaL_prepbuffsize(B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
pub fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, l: usize);
pub fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char);
pub fn luaL_addvalue(B: *mut luaL_Buffer);
pub fn luaL_pushresult(B: *mut luaL_Buffer);
pub fn luaL_pushresultsize(B: *mut luaL_Buffer, sz: usize);
pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
}
// Macro implementations as inline functions
#[inline(always)]
pub unsafe fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char {
luaL_prepbuffsize(B, LUAL_BUFFERSIZE)
}
#[inline(always)]
pub unsafe fn luaL_addchar(B: *mut luaL_Buffer, c: c_char) {
if (*B).n >= (*B).size {
luaL_prepbuffsize(B, 1);
}
*(*B).b.add((*B).n) = c;
(*B).n += 1;
}
#[inline(always)]
pub unsafe fn luaL_addsize(B: *mut luaL_Buffer, n: usize) {
(*B).n += n;
}
+95 -7
View File
@@ -1,7 +1,7 @@
//! Contains definitions from `lauxlib.h`. //! Contains definitions from `lauxlib.h`.
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_double, c_int, c_long, c_void};
use std::ptr; use std::{mem, ptr};
use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State}; use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
@@ -30,7 +30,7 @@ unsafe extern "C-unwind" {
pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int; pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int;
pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char; pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char;
pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize) pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize)
-> *const c_char; -> *const c_char;
pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number; pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number;
pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number; pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number;
pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer; pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer;
@@ -91,7 +91,7 @@ unsafe extern "C-unwind" {
pub fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer; pub fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer;
// TODO: luaL_addgsub pub fn luaL_addgsub(B: *mut luaL_Buffer, s: *const c_char, p: *const c_char, r: *const c_char);
pub fn luaL_gsub( pub fn luaL_gsub(
L: *mut lua_State, L: *mut lua_State,
@@ -162,8 +162,6 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) {
lua::lua_getfield(L, lua::LUA_REGISTRYINDEX, n); lua::lua_getfield(L, lua::LUA_REGISTRYINDEX, n);
} }
// luaL_opt would be implemented here but it is undocumented, so it's omitted
#[inline(always)] #[inline(always)]
pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int { pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int {
luaL_loadbufferx(L, s, sz, n, ptr::null()) luaL_loadbufferx(L, s, sz, n, ptr::null())
@@ -188,6 +186,96 @@ pub unsafe fn luaL_loadbufferenv(
status status
} }
#[inline(always)]
pub unsafe fn luaL_opt<T>(
L: *mut lua_State,
f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T,
n: c_int,
d: T,
) -> T {
if lua::lua_isnoneornil(L, n) != 0 {
d
} else {
f(L, n)
}
}
// //
// TODO: Generic Buffer Manipulation // Generic Buffer Manipulation
// //
// The buffer size used by the lauxlib buffer system.
// LUAL_BUFFERSIZE = (int)(16 * sizeof(void*) * sizeof(lua_Number))
#[rustfmt::skip]
pub const LUAL_BUFFERSIZE: usize = 16 * mem::size_of::<*const ()>() * mem::size_of::<lua_Number>();
// Union used for the initial buffer with maximum alignment.
// This ensures proper alignment for the buffer data.
#[repr(C)]
pub union luaL_BufferInit {
// Alignment matches LUAI_MAXALIGN
pub _align_n: lua_Number,
pub _align_u: c_double,
pub _align_s: *mut c_void,
pub _align_i: lua_Integer,
pub _align_l: c_long,
// Initial buffer space
pub b: [c_char; LUAL_BUFFERSIZE],
}
#[repr(C)]
pub struct luaL_Buffer {
pub b: *mut c_char, // buffer address
pub size: usize, // buffer size
pub n: usize, // number of characters in buffer
pub L: *mut lua_State,
pub init: luaL_BufferInit, // initial buffer (union with alignment)
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer);
pub fn luaL_prepbuffsize(B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
pub fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, l: usize);
pub fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char);
pub fn luaL_addvalue(B: *mut luaL_Buffer);
pub fn luaL_pushresult(B: *mut luaL_Buffer);
pub fn luaL_pushresultsize(B: *mut luaL_Buffer, sz: usize);
pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
}
// Macro implementations as inline functions
#[inline(always)]
pub unsafe fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char {
luaL_prepbuffsize(B, LUAL_BUFFERSIZE)
}
#[inline(always)]
pub unsafe fn luaL_addchar(B: *mut luaL_Buffer, c: c_char) {
if (*B).n >= (*B).size {
luaL_prepbuffsize(B, 1);
}
*(*B).b.add((*B).n) = c;
(*B).n += 1;
}
#[inline(always)]
pub unsafe fn luaL_addsize(B: *mut luaL_Buffer, n: usize) {
(*B).n += n;
}
#[inline(always)]
pub unsafe fn luaL_buffsub(B: *mut luaL_Buffer, n: usize) {
(*B).n -= n;
}
#[inline(always)]
pub unsafe fn luaL_bufflen(B: *mut luaL_Buffer) -> usize {
(*B).n
}
#[inline(always)]
pub unsafe fn luaL_buffaddr(B: *mut luaL_Buffer) -> *mut c_char {
(*B).b
}
+299
View File
@@ -0,0 +1,299 @@
//! Contains definitions from `lauxlib.h`.
use std::os::raw::{c_char, c_double, c_int, c_long, c_uint, c_void};
use std::{mem, ptr};
use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
// Extra error code for 'luaL_loadfilex'
pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1;
// Key, in the registry, for table of loaded modules
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
// Key, in the registry, for table of preloaded loaders
pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD");
#[repr(C)]
pub struct luaL_Reg {
pub name: *const c_char,
pub func: lua_CFunction,
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe 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;
pub fn luaL_callmeta(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
pub fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int;
pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char;
pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize)
-> *const c_char;
pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number;
pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number;
pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer;
pub fn luaL_optinteger(L: *mut lua_State, arg: c_int, def: lua_Integer) -> lua_Integer;
pub fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char);
pub fn luaL_checktype(L: *mut lua_State, arg: c_int, t: c_int);
pub fn luaL_checkany(L: *mut lua_State, arg: c_int);
pub fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_int;
pub fn luaL_setmetatable(L: *mut lua_State, tname: *const c_char);
pub fn luaL_testudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
pub fn luaL_where(L: *mut lua_State, lvl: c_int);
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int;
pub fn luaL_checkoption(
L: *mut lua_State,
arg: c_int,
def: *const c_char,
lst: *const *const c_char,
) -> c_int;
pub fn luaL_fileresult(L: *mut lua_State, stat: c_int, fname: *const c_char) -> c_int;
pub fn luaL_execresult(L: *mut lua_State, stat: c_int) -> c_int;
pub fn luaL_alloc(L: *mut lua_State, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
}
// Pre-defined references
pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe 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);
pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char) -> c_int;
}
#[inline(always)]
pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
luaL_loadfilex(L, f, ptr::null())
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
sz: usize,
name: *const c_char,
mode: *const c_char,
) -> c_int;
pub fn luaL_loadstring(L: *mut lua_State, s: *const c_char) -> c_int;
pub fn luaL_newstate() -> *mut lua_State;
#[link_name = "luaL_makeseed"]
pub fn luaL_makeseed_(L: *mut lua_State) -> c_uint;
pub fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer;
pub fn luaL_addgsub(B: *mut luaL_Buffer, s: *const c_char, p: *const c_char, r: *const c_char);
pub fn luaL_gsub(
L: *mut lua_State,
s: *const c_char,
p: *const c_char,
r: *const c_char,
) -> *const c_char;
pub fn luaL_setfuncs(L: *mut lua_State, l: *const luaL_Reg, nup: c_int);
pub fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_char) -> c_int;
pub fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, level: c_int);
pub fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int);
}
//
// Some useful macros (implemented as Rust functions)
//
// TODO: luaL_newlibtable, luaL_newlib
#[inline(always)]
pub unsafe fn luaL_argcheck(L: *mut lua_State, cond: c_int, arg: c_int, extramsg: *const c_char) {
if cond == 0 {
luaL_argerror(L, arg, extramsg);
}
}
#[inline(always)]
pub unsafe fn luaL_checkstring(L: *mut lua_State, n: c_int) -> *const c_char {
luaL_checklstring(L, n, ptr::null_mut())
}
#[inline(always)]
pub unsafe fn luaL_optstring(L: *mut lua_State, n: c_int, d: *const c_char) -> *const c_char {
luaL_optlstring(L, n, d, ptr::null_mut())
}
#[inline(always)]
pub unsafe fn luaL_typename(L: *mut lua_State, i: c_int) -> *const c_char {
lua::lua_typename(L, lua::lua_type(L, i))
}
#[inline(always)]
pub unsafe fn luaL_dofile(L: *mut lua_State, filename: *const c_char) -> c_int {
let status = luaL_loadfile(L, filename);
if status == 0 {
lua::lua_pcall(L, 0, lua::LUA_MULTRET, 0)
} else {
status
}
}
#[inline(always)]
pub unsafe fn luaL_dostring(L: *mut lua_State, s: *const c_char) -> c_int {
let status = luaL_loadstring(L, s);
if status == 0 {
lua::lua_pcall(L, 0, lua::LUA_MULTRET, 0)
} else {
status
}
}
#[inline(always)]
pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) {
lua::lua_getfield(L, lua::LUA_REGISTRYINDEX, n);
}
#[inline(always)]
pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int {
luaL_loadbufferx(L, s, sz, n, ptr::null())
}
pub unsafe fn luaL_loadbufferenv(
L: *mut lua_State,
data: *const c_char,
size: usize,
name: *const c_char,
mode: *const c_char,
mut env: c_int,
) -> c_int {
if env != 0 {
env = lua::lua_absindex(L, env);
}
let status = luaL_loadbufferx(L, data, size, name, mode);
if status == lua::LUA_OK && env != 0 {
lua::lua_pushvalue(L, env);
lua::lua_setupvalue(L, -2, 1);
}
status
}
pub unsafe fn luaL_makeseed(L: *mut lua_State) -> c_uint {
#[cfg(macos)]
return libc::arc4random();
#[cfg(linux)]
{
let mut seed = 0u32;
let buf = &mut seed as *mut _ as *mut c_void;
if libc::getrandom(buf, 4, libc::GRND_NONBLOCK) == 4 {
return seed;
}
}
luaL_makeseed_(L)
}
#[inline(always)]
pub unsafe fn luaL_opt<T>(
L: *mut lua_State,
f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T,
n: c_int,
d: T,
) -> T {
if lua::lua_isnoneornil(L, n) != 0 {
d
} else {
f(L, n)
}
}
//
// Generic Buffer Manipulation
//
// The buffer size used by the lauxlib buffer system.
// LUAL_BUFFERSIZE = (int)(16 * sizeof(void*) * sizeof(lua_Number))
#[rustfmt::skip]
pub const LUAL_BUFFERSIZE: usize = 16 * mem::size_of::<*const ()>() * mem::size_of::<lua_Number>();
// Union used for the initial buffer with maximum alignment.
// This ensures proper alignment for the buffer data.
#[repr(C)]
pub union luaL_BufferInit {
// Alignment matches LUAI_MAXALIGN
pub _align_n: lua_Number,
pub _align_u: c_double,
pub _align_s: *mut c_void,
pub _align_i: lua_Integer,
pub _align_l: c_long,
// Initial buffer space
pub b: [c_char; LUAL_BUFFERSIZE],
}
#[repr(C)]
pub struct luaL_Buffer {
pub b: *mut c_char, // buffer address
pub size: usize, // buffer size
pub n: usize, // number of characters in buffer
pub L: *mut lua_State,
pub init: luaL_BufferInit, // initial buffer (union with alignment)
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Buffer);
pub fn luaL_prepbuffsize(B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
pub fn luaL_addlstring(B: *mut luaL_Buffer, s: *const c_char, l: usize);
pub fn luaL_addstring(B: *mut luaL_Buffer, s: *const c_char);
pub fn luaL_addvalue(B: *mut luaL_Buffer);
pub fn luaL_pushresult(B: *mut luaL_Buffer);
pub fn luaL_pushresultsize(B: *mut luaL_Buffer, sz: usize);
pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Buffer, sz: usize) -> *mut c_char;
}
// Macro implementations as inline functions
#[inline(always)]
pub unsafe fn luaL_prepbuffer(B: *mut luaL_Buffer) -> *mut c_char {
luaL_prepbuffsize(B, LUAL_BUFFERSIZE)
}
#[inline(always)]
pub unsafe fn luaL_addchar(B: *mut luaL_Buffer, c: c_char) {
if (*B).n >= (*B).size {
luaL_prepbuffsize(B, 1);
}
*(*B).b.add((*B).n) = c;
(*B).n += 1;
}
#[inline(always)]
pub unsafe fn luaL_addsize(B: *mut luaL_Buffer, n: usize) {
(*B).n += n;
}
#[inline(always)]
pub unsafe fn luaL_buffsub(B: *mut luaL_Buffer, n: usize) {
(*B).n -= n;
}
#[inline(always)]
pub unsafe fn luaL_bufflen(B: *mut luaL_Buffer) -> usize {
(*B).n
}
#[inline(always)]
pub unsafe fn luaL_buffaddr(B: *mut luaL_Buffer) -> *mut c_char {
(*B).b
}
+578
View File
@@ -0,0 +1,578 @@
//! Contains definitions from `lua.h`.
use std::ffi::CStr;
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_double, c_int, c_uchar, c_uint, c_void};
use std::{mem, ptr};
// Mark for precompiled code (`<esc>Lua`)
pub const LUA_SIGNATURE: &[u8] = b"\x1bLua";
// Option for multiple returns in 'lua_pcall' and 'lua_call'
pub const LUA_MULTRET: c_int = -1;
// Size of the Lua stack
#[doc(hidden)]
pub const LUAI_MAXSTACK: c_int = c_int::MAX;
// Size of a raw memory area associated with a Lua state with very fast access.
pub const LUA_EXTRASPACE: usize = mem::size_of::<*const ()>();
//
// Pseudo-indices
//
pub const LUA_REGISTRYINDEX: c_int = -(c_int::MAX / 2 + 1000);
pub const fn lua_upvalueindex(i: c_int) -> c_int {
LUA_REGISTRYINDEX - i
}
//
// Thread status
//
pub const LUA_OK: c_int = 0;
pub const LUA_YIELD: c_int = 1;
pub const LUA_ERRRUN: c_int = 2;
pub const LUA_ERRSYNTAX: c_int = 3;
pub const LUA_ERRMEM: c_int = 4;
pub const LUA_ERRERR: c_int = 5;
/// A raw Lua state associated with a thread.
#[repr(C)]
pub struct lua_State {
_data: [u8; 0],
_marker: PhantomData<(*mut u8, PhantomPinned)>,
}
//
// Basic types
//
pub const LUA_TNONE: c_int = -1;
pub const LUA_TNIL: c_int = 0;
pub const LUA_TBOOLEAN: c_int = 1;
pub const LUA_TLIGHTUSERDATA: c_int = 2;
pub const LUA_TNUMBER: c_int = 3;
pub const LUA_TSTRING: c_int = 4;
pub const LUA_TTABLE: c_int = 5;
pub const LUA_TFUNCTION: c_int = 6;
pub const LUA_TUSERDATA: c_int = 7;
pub const LUA_TTHREAD: c_int = 8;
pub const LUA_NUMTYPES: c_int = 9;
/// Minimum Lua stack available to a C function
pub const LUA_MINSTACK: c_int = 20;
// Predefined values in the registry
// index 1 is reserved for the reference mechanism
pub const LUA_RIDX_GLOBALS: lua_Integer = 2;
pub const LUA_RIDX_MAINTHREAD: lua_Integer = 3;
pub const LUA_RIDX_LAST: lua_Integer = 3;
/// A Lua number, usually equivalent to `f64`
pub type lua_Number = c_double;
/// A Lua integer, usually equivalent to `i64`
pub type lua_Integer = i64;
/// A Lua unsigned integer, usually equivalent to `u64`
pub type lua_Unsigned = u64;
/// Type for continuation-function contexts
pub type lua_KContext = isize;
/// Type for native C functions that can be passed to Lua
pub type lua_CFunction = unsafe extern "C-unwind" fn(L: *mut lua_State) -> c_int;
/// Type for continuation functions
pub type lua_KFunction =
unsafe extern "C-unwind" fn(L: *mut lua_State, status: c_int, ctx: lua_KContext) -> c_int;
// Type for functions that read/write blocks when loading/dumping Lua chunks
#[rustfmt::skip]
pub type lua_Reader =
unsafe extern "C-unwind" fn(L: *mut lua_State, ud: *mut c_void, sz: *mut usize) -> *const c_char;
#[rustfmt::skip]
pub type lua_Writer =
unsafe extern "C-unwind" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int;
/// Type for memory-allocation functions (no unwinding)
#[rustfmt::skip]
pub type lua_Alloc =
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
/// Type for warning functions
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 = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
//
// State manipulation
//
pub fn lua_newstate(f: lua_Alloc, ud: *mut c_void, seed: c_uint) -> *mut lua_State;
pub fn lua_close(L: *mut lua_State);
pub fn lua_newthread(L: *mut lua_State) -> *mut lua_State;
pub fn lua_closethread(L: *mut lua_State, from: *mut lua_State) -> c_int;
pub fn lua_atpanic(L: *mut lua_State, panicf: lua_CFunction) -> lua_CFunction;
pub fn lua_version(L: *mut lua_State) -> lua_Number;
//
// Basic stack manipulation
//
pub fn lua_absindex(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_gettop(L: *mut lua_State) -> c_int;
pub fn lua_settop(L: *mut lua_State, idx: c_int);
pub fn lua_pushvalue(L: *mut lua_State, idx: c_int);
pub fn lua_rotate(L: *mut lua_State, idx: c_int, n: c_int);
pub fn lua_copy(L: *mut lua_State, fromidx: c_int, toidx: c_int);
pub fn lua_checkstack(L: *mut lua_State, sz: c_int) -> c_int;
pub fn lua_xmove(from: *mut lua_State, to: *mut lua_State, n: c_int);
//
// Access functions (stack -> C)
//
pub fn lua_isnumber(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_isstring(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_iscfunction(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_isuserdata(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_type(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_typename(L: *mut lua_State, tp: c_int) -> *const c_char;
pub fn lua_tonumberx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Number;
pub fn lua_tointegerx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Integer;
pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
#[link_name = "lua_rawlen"]
fn lua_rawlen_(L: *mut lua_State, idx: c_int) -> lua_Unsigned;
pub fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> Option<lua_CFunction>;
pub fn lua_touserdata(L: *mut lua_State, idx: c_int) -> *mut c_void;
pub fn lua_tothread(L: *mut lua_State, idx: c_int) -> *mut lua_State;
pub fn lua_topointer(L: *mut lua_State, idx: c_int) -> *const c_void;
}
// lua_rawlen's return type changed from size_t to lua_Unsigned int in Lua 5.4.
// This adapts the crate API to the new Lua ABI.
#[inline(always)]
pub unsafe fn lua_rawlen(L: *mut lua_State, idx: c_int) -> usize {
lua_rawlen_(L, idx) as usize
}
//
// Comparison and arithmetic functions
//
pub const LUA_OPADD: c_int = 0;
pub const LUA_OPSUB: c_int = 1;
pub const LUA_OPMUL: c_int = 2;
pub const LUA_OPMOD: c_int = 3;
pub const LUA_OPPOW: c_int = 4;
pub const LUA_OPDIV: c_int = 5;
pub const LUA_OPIDIV: c_int = 6;
pub const LUA_OPBAND: c_int = 7;
pub const LUA_OPBOR: c_int = 8;
pub const LUA_OPBXOR: c_int = 9;
pub const LUA_OPSHL: c_int = 10;
pub const LUA_OPSHR: c_int = 11;
pub const LUA_OPUNM: c_int = 12;
pub const LUA_OPBNOT: c_int = 13;
pub const LUA_OPEQ: c_int = 0;
pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe 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 = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
//
// Push functions (C -> stack)
//
pub fn lua_pushnil(L: *mut lua_State);
pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number);
pub fn lua_pushinteger(L: *mut lua_State, n: lua_Integer);
pub fn lua_pushlstring(L: *mut lua_State, s: *const c_char, len: usize) -> *const c_char;
pub fn lua_pushexternalstring(
L: *mut lua_State,
s: *const c_char,
len: usize,
falloc: Option<lua_Alloc>,
ud: *mut c_void,
) -> *const c_char;
pub fn lua_pushstring(L: *mut lua_State, s: *const c_char) -> *const c_char;
// lua_pushvfstring
pub fn lua_pushfstring(L: *mut lua_State, fmt: *const c_char, ...) -> *const c_char;
pub fn lua_pushcclosure(L: *mut lua_State, f: lua_CFunction, n: c_int);
pub fn lua_pushboolean(L: *mut lua_State, b: c_int);
pub fn lua_pushlightuserdata(L: *mut lua_State, p: *mut c_void);
pub fn lua_pushthread(L: *mut lua_State) -> c_int;
//
// Get functions (Lua -> stack)
//
pub fn lua_getglobal(L: *mut lua_State, name: *const c_char) -> c_int;
pub fn lua_gettable(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_getfield(L: *mut lua_State, idx: c_int, k: *const c_char) -> c_int;
pub fn lua_geti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int;
pub fn lua_rawget(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int;
pub fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_int;
pub fn lua_createtable(L: *mut lua_State, narr: c_int, nrec: c_int);
pub fn lua_newuserdatauv(L: *mut lua_State, sz: usize, nuvalue: c_int) -> *mut c_void;
pub fn lua_getmetatable(L: *mut lua_State, objindex: c_int) -> c_int;
pub fn lua_getiuservalue(L: *mut lua_State, idx: c_int, n: c_int) -> c_int;
//
// Set functions (stack -> Lua)
//
pub fn lua_setglobal(L: *mut lua_State, name: *const c_char);
pub fn lua_settable(L: *mut lua_State, idx: c_int);
pub fn lua_setfield(L: *mut lua_State, idx: c_int, k: *const c_char);
pub fn lua_seti(L: *mut lua_State, idx: c_int, n: lua_Integer);
pub fn lua_rawset(L: *mut lua_State, idx: c_int);
pub fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer);
pub fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void);
pub fn lua_setmetatable(L: *mut lua_State, objindex: c_int) -> c_int;
pub fn lua_setiuservalue(L: *mut lua_State, idx: c_int, n: c_int) -> c_int;
//
// 'load' and 'call' functions (load and run Lua code)
//
pub fn lua_callk(
L: *mut lua_State,
nargs: c_int,
nresults: c_int,
ctx: lua_KContext,
k: Option<lua_KFunction>,
);
pub fn lua_pcallk(
L: *mut lua_State,
nargs: c_int,
nresults: c_int,
errfunc: c_int,
ctx: lua_KContext,
k: Option<lua_KFunction>,
) -> c_int;
pub fn lua_load(
L: *mut lua_State,
reader: lua_Reader,
data: *mut c_void,
chunkname: *const c_char,
mode: *const c_char,
) -> c_int;
pub fn lua_dump(L: *mut lua_State, writer: lua_Writer, data: *mut c_void, strip: c_int) -> c_int;
}
#[inline(always)]
pub unsafe fn lua_call(L: *mut lua_State, n: c_int, r: c_int) {
lua_callk(L, n, r, 0, None)
}
#[inline(always)]
pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_int {
lua_pcallk(L, n, r, f, 0, None)
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
//
// Coroutine functions
//
pub fn lua_yieldk(
L: *mut lua_State,
nresults: c_int,
ctx: lua_KContext,
k: Option<lua_KFunction>,
) -> c_int;
pub fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int;
pub fn lua_status(L: *mut lua_State) -> c_int;
pub fn lua_isyieldable(L: *mut lua_State) -> c_int;
}
#[inline(always)]
pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int {
lua_yieldk(L, n, 0, None)
}
//
// Warning-related functions
//
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe 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);
}
//
// Garbage-collection options
//
pub const LUA_GCSTOP: c_int = 0;
pub const LUA_GCRESTART: c_int = 1;
pub const LUA_GCCOLLECT: c_int = 2;
pub const LUA_GCCOUNT: c_int = 3;
pub const LUA_GCCOUNTB: c_int = 4;
pub const LUA_GCSTEP: c_int = 5;
pub const LUA_GCISRUNNING: c_int = 6;
pub const LUA_GCGEN: c_int = 7;
pub const LUA_GCINC: c_int = 8;
pub const LUA_GCPARAM: c_int = 9;
// Parameters for GC generational mode
pub const LUA_GCPMINORMUL: c_int = 0; // control minor collections
pub const LUA_GCPMAJORMINOR: c_int = 1; // control shift major->minor
pub const LUA_GCPMINORMAJOR: c_int = 2; // control shift minor->major
// Parameters for GC incremental mode
pub const LUA_GCPPAUSE: c_int = 3; // size of pause between successive GCs
pub const LUA_GCPSTEPMUL: c_int = 4; // GC "speed"
pub const LUA_GCPSTEPSIZE: c_int = 5; // GC granularity
pub const LUA_GCPNUM: c_int = 6; // number of parameters
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, ...) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
//
// Miscellaneous functions
//
#[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;
pub fn lua_concat(L: *mut lua_State, n: c_int);
pub fn lua_len(L: *mut lua_State, idx: c_int);
pub fn lua_numbertocstring(L: *mut lua_State, idx: c_int, buff: *mut c_char) -> c_uint;
pub fn lua_stringtonumber(L: *mut lua_State, s: *const c_char) -> usize;
pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc;
pub fn lua_setallocf(L: *mut lua_State, f: lua_Alloc, ud: *mut c_void);
pub fn lua_toclose(L: *mut lua_State, idx: c_int);
pub fn lua_closeslot(L: *mut lua_State, idx: c_int);
}
// lua_error does not return but is declared to return int, and Rust translates
// ! to void which can cause link-time errors if the platform linker is aware
// of return types and requires they match (for example: wasm does this).
#[inline(always)]
pub unsafe fn lua_error(L: *mut lua_State) -> ! {
lua_error_(L);
unreachable!();
}
//
// Some useful macros (implemented as Rust functions)
//
#[inline(always)]
pub unsafe fn lua_getextraspace(L: *mut lua_State) -> *mut c_void {
(L as *mut c_char).sub(LUA_EXTRASPACE) as *mut c_void
}
#[inline(always)]
pub unsafe fn lua_tonumber(L: *mut lua_State, i: c_int) -> lua_Number {
lua_tonumberx(L, i, ptr::null_mut())
}
#[inline(always)]
pub unsafe fn lua_tointeger(L: *mut lua_State, i: c_int) -> lua_Integer {
lua_tointegerx(L, i, ptr::null_mut())
}
#[inline(always)]
pub unsafe fn lua_pop(L: *mut lua_State, n: c_int) {
lua_settop(L, -n - 1)
}
#[inline(always)]
pub unsafe fn lua_newtable(L: *mut lua_State) {
lua_createtable(L, 0, 0)
}
#[inline(always)]
pub unsafe fn lua_register(L: *mut lua_State, n: *const c_char, f: lua_CFunction) {
lua_pushcfunction(L, f);
lua_setglobal(L, n)
}
#[inline(always)]
pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) {
lua_pushcclosure(L, f, 0)
}
#[inline(always)]
pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TFUNCTION) as c_int
}
#[inline(always)]
pub unsafe fn lua_istable(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TTABLE) as c_int
}
#[inline(always)]
pub unsafe fn lua_islightuserdata(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TLIGHTUSERDATA) as c_int
}
#[inline(always)]
pub unsafe fn lua_isnil(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TNIL) as c_int
}
#[inline(always)]
pub unsafe fn lua_isboolean(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TBOOLEAN) as c_int
}
#[inline(always)]
pub unsafe fn lua_isthread(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TTHREAD) as c_int
}
#[inline(always)]
pub unsafe fn lua_isnone(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TNONE) as c_int
}
#[inline(always)]
pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) <= 0) as c_int
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring(L, s.as_ptr());
}
#[inline(always)]
pub unsafe fn lua_pushglobaltable(L: *mut lua_State) -> c_int {
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)
}
#[inline(always)]
pub unsafe fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void {
if lua_islightuserdata(L, idx) != 0 {
return lua_touserdata(L, idx);
}
ptr::null_mut()
}
#[inline(always)]
pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char {
lua_tolstring(L, i, ptr::null_mut())
}
#[inline(always)]
pub unsafe fn lua_insert(L: *mut lua_State, idx: c_int) {
lua_rotate(L, idx, 1)
}
#[inline(always)]
pub unsafe fn lua_remove(L: *mut lua_State, idx: c_int) {
lua_rotate(L, idx, -1);
lua_pop(L, 1)
}
#[inline(always)]
pub unsafe fn lua_replace(L: *mut lua_State, idx: c_int) {
lua_copy(L, -1, idx);
lua_pop(L, 1)
}
#[inline(always)]
pub unsafe fn lua_xpush(from: *mut lua_State, to: *mut lua_State, idx: c_int) {
lua_pushvalue(from, idx);
lua_xmove(from, to, 1);
}
#[inline(always)]
pub unsafe fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void {
lua_newuserdatauv(L, sz, 1)
}
#[inline(always)]
pub unsafe fn lua_getuservalue(L: *mut lua_State, idx: c_int) -> c_int {
lua_getiuservalue(L, idx, 1)
}
#[inline(always)]
pub unsafe fn lua_setuservalue(L: *mut lua_State, idx: c_int) -> c_int {
lua_setiuservalue(L, idx, 1)
}
//
// Debug API
//
// Maximum size for the description of the source of a function in debug information.
const LUA_IDSIZE: usize = 60;
// Event codes
pub const LUA_HOOKCALL: c_int = 0;
pub const LUA_HOOKRET: c_int = 1;
pub const LUA_HOOKLINE: c_int = 2;
pub const LUA_HOOKCOUNT: c_int = 3;
pub const LUA_HOOKTAILCALL: c_int = 4;
// Event masks
pub const LUA_MASKCALL: c_int = 1 << (LUA_HOOKCALL as usize);
pub const LUA_MASKRET: c_int = 1 << (LUA_HOOKRET as usize);
pub const LUA_MASKLINE: c_int = 1 << (LUA_HOOKLINE as usize);
pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
/// Type for functions to be called on debug events.
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 = "lua55", kind = "raw-dylib"))]
unsafe 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;
pub fn lua_setlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
pub fn lua_getupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char;
pub fn lua_setupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char;
pub fn lua_upvalueid(L: *mut lua_State, fidx: c_int, n: c_int) -> *mut c_void;
pub fn lua_upvaluejoin(L: *mut lua_State, fidx1: c_int, n1: c_int, fidx2: c_int, n2: c_int);
pub fn lua_sethook(L: *mut lua_State, func: Option<lua_Hook>, mask: c_int, count: c_int);
pub fn lua_gethook(L: *mut lua_State) -> Option<lua_Hook>;
pub fn lua_gethookmask(L: *mut lua_State) -> c_int;
pub fn lua_gethookcount(L: *mut lua_State) -> c_int;
}
#[repr(C)]
pub struct lua_Debug {
pub event: c_int,
pub name: *const c_char, // (n)
pub namewhat: *const c_char, // (n) 'global', 'local', 'field', 'method'
pub what: *const c_char, // (S) 'Lua', 'C', 'main', 'tail'
pub source: *const c_char, // (S)
pub srclen: usize, // (S)
pub currentline: c_int, // (l)
pub linedefined: c_int, // (S)
pub lastlinedefined: c_int, // (S)
pub nups: c_uchar, // (u) number of upvalues
pub nparams: c_uchar, // (u) number of parameters
pub isvararg: c_char, // (u)
pub extraargs: c_uchar, // (t) number of extra arguments
pub istailcall: c_char, // (t)
pub ftransfer: c_int, // (r) index of first value transferred
pub ntransfer: c_int, // (r) number of transferred values
pub short_src: [c_char; LUA_IDSIZE], // (S)
// lua.h mentions this is for private use
i_ci: *mut c_void,
}
+55
View File
@@ -0,0 +1,55 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::{c_char, c_int};
use super::lua::lua_State;
pub const LUA_GLIBK: c_int = 1;
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
pub const LUA_LOADLIBK: c_int = LUA_GLIBK << 1;
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_COLIBK: c_int = LUA_GLIBK << 2;
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_DBLIBK: c_int = LUA_GLIBK << 3;
pub const LUA_IOLIBNAME: *const c_char = cstr!("io");
pub const LUA_IOLIBK: c_int = LUA_GLIBK << 4;
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_MATHLIBK: c_int = LUA_GLIBK << 5;
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_OSLIBK: c_int = LUA_GLIBK << 6;
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_STRLIBK: c_int = LUA_GLIBK << 7;
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_TABLIBK: c_int = LUA_GLIBK << 8;
pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8");
pub const LUA_UTF8LIBK: c_int = LUA_GLIBK << 9;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua55", kind = "raw-dylib"))]
unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_package(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_debug(L: *mut lua_State) -> c_int;
pub fn luaopen_io(L: *mut lua_State) -> c_int;
pub fn luaopen_math(L: *mut lua_State) -> c_int;
pub fn luaopen_os(L: *mut lua_State) -> c_int;
pub fn luaopen_string(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
pub fn luaopen_utf8(L: *mut lua_State) -> c_int;
// open all builtin libraries
pub fn luaL_openselectedlibs(L: *mut lua_State, load: c_int, preload: c_int);
}
pub unsafe fn luaL_openlibs(L: *mut lua_State) {
luaL_openselectedlibs(L, !0, 0);
}
+9
View File
@@ -0,0 +1,9 @@
//! Low level bindings to Lua 5.5.
pub use lauxlib::*;
pub use lua::*;
pub use lualib::*;
pub mod lauxlib;
pub mod lua;
pub mod lualib;
+68 -47
View File
@@ -23,8 +23,8 @@ unsafe fn compat53_reverse(L: *mut lua_State, mut a: c_int, mut b: c_int) {
} }
} }
const COMPAT53_LEVELS1: c_int = 12; // size of the first part of the stack const COMPAT53_LEVELS1: c_int = 10; // size of the first part of the stack
const COMPAT53_LEVELS2: c_int = 10; // size of the second part of the stack const COMPAT53_LEVELS2: c_int = 11; // size of the second part of the stack
unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) -> c_int { unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) -> c_int {
if level == 0 || lua_istable(L, -1) == 0 { if level == 0 || lua_istable(L, -1) == 0 {
@@ -41,11 +41,10 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) ->
lua_pop(L, 1); // remove value (but keep name) lua_pop(L, 1); // remove value (but keep name)
return 1; return 1;
} else if compat53_findfield(L, objidx, level - 1) != 0 { } else if compat53_findfield(L, objidx, level - 1) != 0 {
// try recursively // stack: lib_name, lib_table, field_name (top)
lua_remove(L, -2); // remove table (but keep name) lua_pushliteral(L, c"."); // place '.' between the two names
lua_pushliteral(L, c"."); lua_replace(L, -3); // (in the slot occupied by table)
lua_insert(L, -2); // place '.' between the two names lua_concat(L, 3); // lib_name.field_name
lua_concat(L, 3);
return 1; return 1;
} }
} }
@@ -54,14 +53,25 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) ->
0 // not found 0 // not found
} }
unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int { unsafe fn compat53_pushglobalfuncname(
L: *mut lua_State,
L1: *mut lua_State,
level: c_int,
ar: *mut lua_Debug,
) -> c_int {
let top = lua_gettop(L); let top = lua_gettop(L);
// push function lua_getinfo(L1, level, cstr!("f"), ar); // push function
lua_getinfo(L, level, cstr!("f"), ar); lua_xmove(L1, L, 1); // and move onto L
lua_pushvalue(L, LUA_GLOBALSINDEX); lua_pushvalue(L, LUA_GLOBALSINDEX);
luaL_checkstack(L, 6, cstr!("not enough stack")); // slots for 'findfield'
if compat53_findfield(L, top + 1, 2) != 0 { if compat53_findfield(L, top + 1, 2) != 0 {
let name = lua_tostring(L, -1);
if CStr::from_ptr(name).to_bytes().starts_with(b"_G.") {
lua_pushstring(L, name.add(3)); // push name without prefix
lua_remove(L, -2); // remove original name
}
lua_copy(L, -1, top + 1); // move name to proper place lua_copy(L, -1, top + 1); // move name to proper place
lua_pop(L, 2); // remove pushed values lua_settop(L, top + 1); // remove pushed values
1 1
} else { } else {
lua_settop(L, top); // remove function and global table lua_settop(L, top); // remove function and global table
@@ -69,13 +79,16 @@ unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, level: c_int, ar: *mut
} }
} }
unsafe fn compat53_pushfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) { unsafe fn compat53_pushfuncname(L: *mut lua_State, L1: *mut lua_State, level: c_int, ar: *mut lua_Debug) {
if !(*ar).name.is_null() { if !(*ar).name.is_null() {
// is there a name? // is there a name?
lua_pushfstring(L, cstr!("function '%s'"), (*ar).name); lua_pushfstring(L, cstr!("function '%s'"), (*ar).name);
} else if compat53_pushglobalfuncname(L, level, ar) != 0 { } else if compat53_pushglobalfuncname(L, L1, level, ar) != 0 {
lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1)); lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1));
lua_remove(L, -2); // remove name lua_remove(L, -2); // remove name
} else if *(*ar).what != b'C' as c_char {
// for Lua functions, use <file:line>
lua_pushfstring(L, cstr!("function <%s:%d>"), (*ar).short_src, (*ar).linedefined);
} else { } else {
lua_pushliteral(L, c"?"); lua_pushliteral(L, c"?");
} }
@@ -120,7 +133,8 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
if lua_type(L, idx) == LUA_TNUMBER { if lua_type(L, idx) == LUA_TNUMBER {
let n = lua_tonumber(L, idx); let n = lua_tonumber(L, idx);
let i = lua_tointeger(L, idx); let i = lua_tointeger(L, idx);
if (n - i as lua_Number).abs() < lua_Number::EPSILON { // Lua 5.3+ returns "false" for `-0.0`
if n.to_bits() == (i as lua_Number).to_bits() {
return 1; return 1;
} }
} }
@@ -189,9 +203,7 @@ pub unsafe fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_in
#[inline(always)] #[inline(always)]
pub unsafe fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_int { pub unsafe fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_int {
let abs_i = lua_absindex(L, idx); lua_rawgetptagged(L, idx, p, 0)
lua_pushlightuserdata(L, p as *mut c_void);
lua_rawget(L, abs_i)
} }
#[inline(always)] #[inline(always)]
@@ -225,11 +237,7 @@ pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) {
#[inline(always)] #[inline(always)]
pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) { pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) {
let abs_i = lua_absindex(L, idx); lua_rawsetptagged(L, idx, p, 0)
luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
lua_pushlightuserdata(L, p as *mut c_void);
lua_insert(L, -2);
lua_rawset(L, abs_i);
} }
#[inline(always)] #[inline(always)]
@@ -388,7 +396,7 @@ pub unsafe fn luaL_loadbufferenv(
} }
} }
if chunk_is_text { let status = if chunk_is_text {
if env < 0 { if env < 0 {
env -= 1; env -= 1;
} }
@@ -397,14 +405,21 @@ pub unsafe fn luaL_loadbufferenv(
ptr::write(data_ud, data); ptr::write(data_ud, data);
// By deferring the `free(data)` to the userdata destructor, we ensure that // By deferring the `free(data)` to the userdata destructor, we ensure that
// even if `luau_load` throws an error, the `data` is still released. // even if `luau_load` throws an error, the `data` is still released.
let ok = luau_load(L, name, data, size, env) == 0; let status = luau_load(L, name, data, size, env);
lua_replace(L, -2); // replace data with the result lua_replace(L, -2); // replace data with the result
if !ok { status
return LUA_ERRSYNTAX; } else {
luau_load(L, name, data, size, env)
};
if status != 0 {
if lua_isstring(L, -1) != 0 && CStr::from_ptr(lua_tostring(L, -1)) == c"not enough memory" {
// A case for Luau >= 0.679
return LUA_ERRMEM;
} }
} else if luau_load(L, name, data, size, env) != 0 {
return LUA_ERRSYNTAX; return LUA_ERRSYNTAX;
} }
LUA_OK LUA_OK
} }
@@ -444,36 +459,42 @@ pub unsafe fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer {
pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, mut level: c_int) { pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, mut level: c_int) {
let mut ar: lua_Debug = mem::zeroed(); let mut ar: lua_Debug = mem::zeroed();
let top = lua_gettop(L);
let numlevels = lua_stackdepth(L); let numlevels = lua_stackdepth(L);
let mark = if numlevels > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 { #[rustfmt::skip]
COMPAT53_LEVELS1 let mut limit = if numlevels - level > COMPAT53_LEVELS1 + COMPAT53_LEVELS2 { COMPAT53_LEVELS1 } else { -1 };
} else {
0 let mut buf: luaL_Strbuf = mem::zeroed();
}; luaL_buffinit(L, &mut buf);
if !msg.is_null() { if !msg.is_null() {
lua_pushfstring(L, cstr!("%s\n"), msg); luaL_addstring(&mut buf, msg);
luaL_addstring(&mut buf, cstr!("\n"));
} }
lua_pushliteral(L, c"stack traceback:"); luaL_addstring(&mut buf, cstr!("stack traceback:"));
while lua_getinfo(L1, level, cstr!(""), &mut ar) != 0 { while lua_getinfo(L1, level, cstr!("sln"), &mut ar) != 0 {
if level + 1 == mark { if limit == 0 {
// too many levels? // too many levels?
lua_pushliteral(L, c"\n\t..."); // add a '...' let n = numlevels - level - COMPAT53_LEVELS2;
level = numlevels - COMPAT53_LEVELS2; // and skip to last ones // add warning about skip ("n + 1" because we skip current level too)
lua_pushfstring(L, cstr!("\n\t...\t(skipping %d levels)"), n + 1);
luaL_addvalue(&mut buf);
level += n; // and skip to last levels
} else { } else {
lua_getinfo(L1, level, cstr!("sln"), &mut ar); luaL_addstring(&mut buf, cstr!("\n\t"));
lua_pushfstring(L, cstr!("\n\t%s:"), ar.short_src); luaL_addstring(&mut buf, ar.short_src);
luaL_addstring(&mut buf, cstr!(":"));
if ar.currentline > 0 { if ar.currentline > 0 {
lua_pushfstring(L, cstr!("%d:"), ar.currentline); luaL_addunsigned(&mut buf, ar.currentline as _);
luaL_addstring(&mut buf, cstr!(":"));
} }
lua_pushliteral(L, c" in "); luaL_addstring(&mut buf, cstr!(" in "));
compat53_pushfuncname(L, level, &mut ar); compat53_pushfuncname(L, L1, level, &mut ar);
lua_concat(L, lua_gettop(L) - top); luaL_addvalue(&mut buf);
} }
level += 1; level += 1;
limit -= 1;
} }
lua_concat(L, lua_gettop(L) - top); luaL_pushresult(&mut buf);
} }
pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) -> *const c_char { pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) -> *const c_char {
@@ -536,7 +557,7 @@ pub unsafe fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_ch
pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) { pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) {
luaL_checkstack(L, 3, cstr!("not enough stack slots available")); luaL_checkstack(L, 3, cstr!("not enough stack slots available"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED")); luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
if lua_getfield(L, -1, modname) == LUA_TNIL { if lua_getfield(L, -1, modname) == LUA_TNIL {
lua_pop(L, 1); lua_pop(L, 1);
lua_pushcfunction(L, openf); lua_pushcfunction(L, openf);
+35 -2
View File
@@ -3,7 +3,10 @@
use std::os::raw::{c_char, c_float, c_int, c_void}; use std::os::raw::{c_char, c_float, c_int, c_void};
use std::ptr; use std::ptr;
use super::lua::{self, lua_CFunction, lua_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX}; use super::lua::{self, LUA_REGISTRYINDEX, lua_CFunction, lua_Number, lua_State, lua_Unsigned};
// Key, in the registry, for table of loaded modules
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
#[repr(C)] #[repr(C)]
pub struct luaL_Reg { pub struct luaL_Reg {
@@ -82,6 +85,9 @@ unsafe extern "C-unwind" {
pub fn luaL_callyieldable(L: *mut lua_State, nargs: c_int, nresults: c_int) -> c_int; pub fn luaL_callyieldable(L: *mut lua_State, nargs: c_int, nresults: c_int) -> c_int;
#[link_name = "luaL_traceback"]
pub fn luaL_traceback_(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, level: c_int);
// sandbox libraries and globals // sandbox libraries and globals
#[link_name = "luaL_sandbox"] #[link_name = "luaL_sandbox"]
pub fn luaL_sandbox_(L: *mut lua_State); pub fn luaL_sandbox_(L: *mut lua_State);
@@ -116,7 +122,19 @@ pub unsafe fn luaL_optstring(L: *mut lua_State, n: c_int, d: *const c_char) -> *
luaL_optlstring(L, n, d, ptr::null_mut()) luaL_optlstring(L, n, d, ptr::null_mut())
} }
// TODO: luaL_opt #[inline(always)]
pub unsafe fn luaL_opt<T>(
L: *mut lua_State,
f: unsafe extern "C-unwind" fn(*mut lua_State, c_int) -> T,
n: c_int,
d: T,
) -> T {
if lua::lua_isnoneornil(L, n) != 0 {
d
} else {
f(L, n)
}
}
#[inline(always)] #[inline(always)]
pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) -> c_int { pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) -> c_int {
@@ -209,3 +227,18 @@ pub unsafe fn luaL_addstring(B: *mut luaL_Strbuf, s: *const c_char) {
} }
luaL_addlstring(B, s, len); luaL_addlstring(B, s, len);
} }
pub unsafe fn luaL_addunsigned(B: *mut luaL_Strbuf, mut n: lua_Unsigned) {
let mut buf: [c_char; 32] = [0; 32];
let mut i = 32;
loop {
i -= 1;
let digit = (n % 10) as u8;
buf[i] = (b'0' + digit) as c_char;
n /= 10;
if n == 0 {
break;
}
}
luaL_addlstring(B, buf.as_ptr().add(i), 32 - i);
}
+39 -3
View File
@@ -37,6 +37,16 @@ pub const LUA_ERRRUN: c_int = 2;
pub const LUA_ERRSYNTAX: c_int = 3; pub const LUA_ERRSYNTAX: c_int = 3;
pub const LUA_ERRMEM: c_int = 4; pub const LUA_ERRMEM: c_int = 4;
pub const LUA_ERRERR: c_int = 5; pub const LUA_ERRERR: c_int = 5;
pub const LUA_BREAK: c_int = 6; // yielded for a debug breakpoint
//
// Coroutine status
//
pub const LUA_CORUN: c_int = 0; // running
pub const LUA_COSUS: c_int = 1; // suspended
pub const LUA_CONOR: c_int = 2; // 'normal' (it resumed another coroutine)
pub const LUA_COFIN: c_int = 3; // finished
pub const LUA_COERR: c_int = 4; // finished with error
/// A raw Lua state associated with a thread. /// A raw Lua state associated with a thread.
#[repr(C)] #[repr(C)]
@@ -145,8 +155,15 @@ unsafe extern "C-unwind" {
pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char; pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
pub fn lua_tostringatom(L: *mut lua_State, idx: c_int, atom: *mut c_int) -> *const c_char; pub fn lua_tostringatom(L: *mut lua_State, idx: c_int, atom: *mut c_int) -> *const c_char;
pub fn lua_tolstringatom(
L: *mut lua_State,
idx: c_int,
len: *mut usize,
atom: *mut c_int,
) -> *const c_char;
pub fn lua_namecallatom(L: *mut lua_State, atom: *mut c_int) -> *const c_char; pub fn lua_namecallatom(L: *mut lua_State, atom: *mut c_int) -> *const c_char;
pub fn lua_objlen(L: *mut lua_State, idx: c_int) -> usize; #[link_name = "lua_objlen"]
pub fn lua_objlen_(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> Option<lua_CFunction>; pub fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> Option<lua_CFunction>;
pub fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void; pub fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void;
pub fn lua_tolightuserdatatagged(L: *mut lua_State, idx: c_int, tag: c_int) -> *mut c_void; pub fn lua_tolightuserdatatagged(L: *mut lua_State, idx: c_int, tag: c_int) -> *mut c_void;
@@ -203,6 +220,7 @@ unsafe extern "C-unwind" {
pub fn lua_rawget(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_rawget(L: *mut lua_State, idx: c_int) -> c_int;
#[link_name = "lua_rawgeti"] #[link_name = "lua_rawgeti"]
pub fn lua_rawgeti_(L: *mut lua_State, idx: c_int, n: c_int) -> c_int; pub fn lua_rawgeti_(L: *mut lua_State, idx: c_int, n: c_int) -> c_int;
pub fn lua_rawgetptagged(L: *mut lua_State, idx: c_int, p: *const c_void, tag: c_int) -> c_int;
pub fn lua_createtable(L: *mut lua_State, narr: c_int, nrec: c_int); pub fn lua_createtable(L: *mut lua_State, narr: c_int, nrec: c_int);
pub fn lua_setreadonly(L: *mut lua_State, idx: c_int, enabled: c_int); pub fn lua_setreadonly(L: *mut lua_State, idx: c_int, enabled: c_int);
@@ -217,9 +235,11 @@ unsafe extern "C-unwind" {
// //
pub fn lua_settable(L: *mut lua_State, idx: c_int); pub fn lua_settable(L: *mut lua_State, idx: c_int);
pub fn lua_setfield(L: *mut lua_State, idx: c_int, k: *const c_char); pub fn lua_setfield(L: *mut lua_State, idx: c_int, k: *const c_char);
pub fn lua_rawsetfield(L: *mut lua_State, idx: c_int, k: *const c_char);
pub fn lua_rawset(L: *mut lua_State, idx: c_int); pub fn lua_rawset(L: *mut lua_State, idx: c_int);
#[link_name = "lua_rawseti"] #[link_name = "lua_rawseti"]
pub fn lua_rawseti_(L: *mut lua_State, idx: c_int, n: c_int); pub fn lua_rawseti_(L: *mut lua_State, idx: c_int, n: c_int);
pub fn lua_rawsetptagged(L: *mut lua_State, idx: c_int, p: *const c_void, tag: c_int);
pub fn lua_setmetatable(L: *mut lua_State, objindex: c_int) -> c_int; pub fn lua_setmetatable(L: *mut lua_State, objindex: c_int) -> c_int;
pub fn lua_setfenv(L: *mut lua_State, idx: c_int) -> c_int; pub fn lua_setfenv(L: *mut lua_State, idx: c_int) -> c_int;
@@ -235,6 +255,7 @@ unsafe extern "C-unwind" {
) -> c_int; ) -> c_int;
pub fn lua_call(L: *mut lua_State, nargs: c_int, nresults: c_int); pub fn lua_call(L: *mut lua_State, nargs: c_int, nresults: c_int);
pub fn lua_pcall(L: *mut lua_State, nargs: c_int, nresults: c_int, errfunc: c_int) -> c_int; pub fn lua_pcall(L: *mut lua_State, nargs: c_int, nresults: c_int, errfunc: c_int) -> c_int;
pub fn lua_cpcall(L: *mut lua_State, f: lua_CFunction, ud: *mut c_void) -> c_int;
// //
// Coroutine functions // Coroutine functions
@@ -248,6 +269,12 @@ unsafe extern "C-unwind" {
pub fn lua_isyieldable(L: *mut lua_State) -> c_int; pub fn lua_isyieldable(L: *mut lua_State) -> c_int;
pub fn lua_getthreaddata(L: *mut lua_State) -> *mut c_void; pub fn lua_getthreaddata(L: *mut lua_State) -> *mut c_void;
pub fn lua_setthreaddata(L: *mut lua_State, data: *mut c_void); pub fn lua_setthreaddata(L: *mut lua_State, data: *mut c_void);
pub fn lua_costatus(L: *mut lua_State, co: *mut lua_State) -> c_int;
}
#[inline(always)]
pub unsafe fn lua_objlen(L: *mut lua_State, idx: c_int) -> usize {
lua_objlen_(L, idx) as usize
} }
// //
@@ -284,7 +311,7 @@ unsafe extern "C-unwind" {
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int; 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; pub fn lua_rawiter(L: *mut lua_State, idx: c_int, iter: c_int) -> c_int;
pub fn lua_concat(L: *mut lua_State, n: c_int); pub fn lua_concat(L: *mut lua_State, n: c_int);
// TODO: lua_encodepointer pub fn lua_encodepointer(L: *mut lua_State, p: usize) -> usize;
pub fn lua_clock() -> c_double; pub fn lua_clock() -> c_double;
pub fn lua_setuserdatatag(L: *mut lua_State, idx: c_int, tag: c_int); pub fn lua_setuserdatatag(L: *mut lua_State, idx: c_int, tag: c_int);
pub fn lua_setuserdatadtor(L: *mut lua_State, tag: c_int, dtor: Option<lua_Destructor>); pub fn lua_setuserdatadtor(L: *mut lua_State, tag: c_int, dtor: Option<lua_Destructor>);
@@ -295,6 +322,7 @@ unsafe extern "C-unwind" {
pub fn lua_getlightuserdataname(L: *mut lua_State, tag: c_int) -> *const c_char; pub fn lua_getlightuserdataname(L: *mut lua_State, tag: c_int) -> *const c_char;
pub fn lua_clonefunction(L: *mut lua_State, idx: c_int); pub fn lua_clonefunction(L: *mut lua_State, idx: c_int);
pub fn lua_cleartable(L: *mut lua_State, idx: c_int); pub fn lua_cleartable(L: *mut lua_State, idx: c_int);
pub fn lua_clonetable(L: *mut lua_State, idx: c_int);
pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc; pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc;
} }
@@ -354,7 +382,10 @@ pub unsafe fn lua_newuserdata_t<T>(L: *mut lua_State, data: T) -> *mut T {
ud_ptr ud_ptr
} }
// TODO: lua_strlen #[inline(always)]
pub unsafe fn lua_strlen(L: *mut lua_State, i: c_int) -> usize {
lua_objlen(L, i)
}
#[inline(always)] #[inline(always)]
pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int { pub unsafe fn lua_isfunction(L: *mut lua_State, n: c_int) -> c_int {
@@ -544,4 +575,9 @@ unsafe extern "C" {
unsafe extern "C" { unsafe extern "C" {
pub fn luau_setfflag(name: *const c_char, value: c_int) -> c_int; 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; pub fn lua_getmetatablepointer(L: *mut lua_State, idx: c_int) -> *const c_void;
pub fn lua_gcdump(
L: *mut lua_State,
file: *mut c_void,
category_name: Option<unsafe extern "C" fn(L: *mut lua_State, memcat: u8) -> *const c_char>,
);
} }
+92 -27
View File
@@ -23,14 +23,27 @@ pub enum luarequire_WriteResult {
Failure, Failure,
} }
/// Represents whether a configuration file is present, and if so, its syntax.
#[repr(C)]
pub enum luarequire_ConfigStatus {
Absent,
// Signals the presence of multiple configuration files
Ambiguous,
PresentJson,
PresentLuau,
}
#[repr(C)] #[repr(C)]
pub struct luarequire_Configuration { pub struct luarequire_Configuration {
// Returns whether requires are permitted from the given chunkname. // Returns whether requires are permitted from the given chunkname.
pub is_require_allowed: pub is_require_allowed: unsafe extern "C-unwind" fn(
unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void, requirer_chunkname: *const c_char) -> bool, L: *mut lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> bool,
// Resets the internal state to point at the requirer module. // Resets the internal state to point at the requirer module.
pub reset: unsafe extern "C" fn( pub reset: unsafe extern "C-unwind" fn(
L: *mut lua_State, L: *mut lua_State,
ctx: *mut c_void, ctx: *mut c_void,
requirer_chunkname: *const c_char, requirer_chunkname: *const c_char,
@@ -39,26 +52,50 @@ pub struct luarequire_Configuration {
// Resets the internal state to point at an aliased module, given its exact path from a configuration // Resets the internal state to point at an aliased module, given its exact path from a configuration
// file. This function is only called when an alias's path cannot be resolved relative to its // file. This function is only called when an alias's path cannot be resolved relative to its
// configuration file. // configuration file.
pub jump_to_alias: unsafe extern "C" fn( pub jump_to_alias: unsafe extern "C-unwind" fn(
L: *mut lua_State, L: *mut lua_State,
ctx: *mut c_void, ctx: *mut c_void,
path: *const c_char, path: *const c_char,
) -> luarequire_NavigateResult, ) -> luarequire_NavigateResult,
// Provides an initial alias override opportunity prior to searching for configuration files.
// If NAVIGATE_SUCCESS is returned, the internal state must be updated to point at the
// aliased location.
// Can be left undefined.
pub to_alias_override: Option<
unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
alias_unprefixed: *const c_char,
) -> luarequire_NavigateResult,
>,
// Provides a final override opportunity if an alias cannot be found in configuration files. If
// NAVIGATE_SUCCESS is returned, this must update the internal state to point at the aliased module.
// Can be left undefined.
pub to_alias_fallback: Option<
unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
alias_unprefixed: *const c_char,
) -> luarequire_NavigateResult,
>,
// Navigates through the context by making mutations to the internal state. // Navigates through the context by making mutations to the internal state.
pub to_parent: unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void) -> luarequire_NavigateResult, pub to_parent:
pub to_child: unsafe extern "C" fn( unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> luarequire_NavigateResult,
pub to_child: unsafe extern "C-unwind" fn(
L: *mut lua_State, L: *mut lua_State,
ctx: *mut c_void, ctx: *mut c_void,
name: *const c_char, name: *const c_char,
) -> luarequire_NavigateResult, ) -> luarequire_NavigateResult,
// Returns whether the context is currently pointing at a module. // Returns whether the context is currently pointing at a module.
pub is_module_present: unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void) -> bool, pub is_module_present: unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> bool,
// Provides the contents of the current module. This function is only called if is_module_present returns // Provides a chunkname for the current module. This will be accessible through the debug library. This
// true. // function is only called if is_module_present returns true.
pub get_contents: unsafe extern "C" fn( pub get_chunkname: unsafe extern "C-unwind" fn(
L: *mut lua_State, L: *mut lua_State,
ctx: *mut c_void, ctx: *mut c_void,
buffer: *mut c_char, buffer: *mut c_char,
@@ -66,9 +103,9 @@ pub struct luarequire_Configuration {
size_out: *mut usize, size_out: *mut usize,
) -> luarequire_WriteResult, ) -> luarequire_WriteResult,
// Provides a chunkname for the current module. This will be accessible through the debug library. This // Provides a loadname that identifies the current module and is passed to load. This function
// function is only called if is_module_present returns true. // is only called if is_module_present returns true.
pub get_chunkname: unsafe extern "C" fn( pub get_loadname: unsafe extern "C-unwind" fn(
L: *mut lua_State, L: *mut lua_State,
ctx: *mut c_void, ctx: *mut c_void,
buffer: *mut c_char, buffer: *mut c_char,
@@ -78,7 +115,7 @@ pub struct luarequire_Configuration {
// Provides a cache key representing the current module. This function is only called if // Provides a cache key representing the current module. This function is only called if
// is_module_present returns true. // is_module_present returns true.
pub get_cache_key: unsafe extern "C" fn( pub get_cache_key: unsafe extern "C-unwind" fn(
L: *mut lua_State, L: *mut lua_State,
ctx: *mut c_void, ctx: *mut c_void,
buffer: *mut c_char, buffer: *mut c_char,
@@ -86,20 +123,48 @@ pub struct luarequire_Configuration {
size_out: *mut usize, size_out: *mut usize,
) -> luarequire_WriteResult, ) -> luarequire_WriteResult,
// Returns whether a configuration file is present in the current context. // Returns whether a configuration file is present in the current context, and if so, its syntax.
// If not, require-by-string will call to_parent until either a configuration file is present or // If not present, require-by-string will call to_parent until either a configuration file is present or
// NAVIGATE_FAILURE is returned (at root). // NAVIGATE_FAILURE is returned (at root).
pub is_config_present: unsafe extern "C" fn(L: *mut lua_State, ctx: *mut c_void) -> bool, pub get_config_status:
unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> luarequire_ConfigStatus,
// Parses the configuration file in the current context for the given alias and returns its
// value or WRITE_FAILURE if not found. This function is only called if get_config_status
// returns true. If this function pointer is set, get_config must not be set. Opting in to this
// function pointer disables parsing configuration files internally and can be used for finer
// control over the configuration file parsing process.
pub get_alias: Option<
unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
alias: *const c_char,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
>,
// Provides the contents of the configuration file in the current context. // Provides the contents of the configuration file in the current context.
// This function is only called if is_config_present returns true. // This function is only called if get_config_status does not return CONFIG_ABSENT. If this function
pub get_config: unsafe extern "C" fn( // pointer is set, get_alias must not be set. Opting in to this function pointer enables parsing
L: *mut lua_State, // configuration files internally.
ctx: *mut c_void, pub get_config: Option<
buffer: *mut c_char, unsafe extern "C-unwind" fn(
buffer_size: usize, L: *mut lua_State,
size_out: *mut usize, ctx: *mut c_void,
) -> luarequire_WriteResult, buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
>,
// Returns the maximum number of milliseconds to allow for executing a given Luau-syntax configuration
// file. This function is only called if get_config_status returns CONFIG_PRESENT_LUAU and can be left
// undefined if support for Luau-syntax configuration files is not needed. A default value of 2000ms is
// used. Negative values are treated as infinite.
pub get_luau_config_timeout:
Option<unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> c_int>,
// Executes the module and places the result on the stack. Returns the number of results placed on the // Executes the module and places the result on the stack. Returns the number of results placed on the
// stack. // stack.
@@ -110,12 +175,12 @@ pub struct luarequire_Configuration {
ctx: *mut c_void, ctx: *mut c_void,
path: *const c_char, path: *const c_char,
chunkname: *const c_char, chunkname: *const c_char,
contents: *const c_char, loadname: *const c_char,
) -> c_int, ) -> c_int,
} }
// Populates function pointers in the given luarequire_Configuration. // Populates function pointers in the given luarequire_Configuration.
pub type luarequire_Configuration_init = unsafe extern "C" fn(config: *mut luarequire_Configuration); pub type luarequire_Configuration_init = unsafe extern "C-unwind" fn(config: *mut luarequire_Configuration);
unsafe extern "C-unwind" { unsafe extern "C-unwind" {
// Initializes and pushes the require closure onto the stack without registration. // Initializes and pushes the require closure onto the stack without registration.
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "mlua_derive" name = "mlua_derive"
version = "0.11.0-beta.1" version = "0.11.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"] authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2021" edition = "2021"
description = "Procedural macros for the mlua crate." description = "Procedural macros for the mlua crate."
+4 -4
View File
@@ -120,7 +120,7 @@ pub fn chunk(input: TokenStream) -> TokenStream {
Some(ChunkMode::Text) Some(ChunkMode::Text)
} }
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> { fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed((#source).as_bytes())) Ok(Cow::Borrowed((#source).as_bytes()))
} }
} }
@@ -129,13 +129,13 @@ pub fn chunk(input: TokenStream) -> TokenStream {
let globals = lua.globals(); let globals = lua.globals();
let env = lua.create_table()?; let env = lua.create_table()?;
let meta = lua.create_table()?; let meta = lua.create_table()?;
meta.raw_set("__index", globals.clone())?; meta.raw_set("__index", &globals)?;
meta.raw_set("__newindex", globals)?; meta.raw_set("__newindex", &globals)?;
// Add captured variables // Add captured variables
#(#caps)* #(#caps)*
env.set_metatable(Some(meta)); env.set_metatable(Some(meta))?;
Ok(env) Ok(env)
}; };
+98 -17
View File
@@ -1,6 +1,9 @@
#[cfg(feature = "serialize")] use std::io;
#[cfg(feature = "serde")]
use serde::ser::{Serialize, Serializer}; use serde::ser::{Serialize, Serializer};
use crate::state::RawLua;
use crate::types::ValueRef; use crate::types::ValueRef;
/// A Luau buffer type. /// A Luau buffer type.
@@ -16,16 +19,17 @@ pub struct Buffer(pub(crate) ValueRef);
impl Buffer { impl Buffer {
/// Copies the buffer data into a new `Vec<u8>`. /// Copies the buffer data into a new `Vec<u8>`.
pub fn to_vec(&self) -> Vec<u8> { pub fn to_vec(&self) -> Vec<u8> {
unsafe { self.as_slice().to_vec() } let lua = self.0.lua.lock();
self.as_slice(&lua).to_vec()
} }
/// Returns the length of the buffer. /// Returns the length of the buffer.
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
unsafe { self.as_slice().len() } let lua = self.0.lua.lock();
self.as_slice(&lua).len()
} }
/// Returns `true` if the buffer is empty. /// Returns `true` if the buffer is empty.
#[doc(hidden)]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.len() == 0 self.len() == 0
} }
@@ -35,7 +39,8 @@ impl Buffer {
/// Offset is 0-based. /// Offset is 0-based.
#[track_caller] #[track_caller]
pub fn read_bytes<const N: usize>(&self, offset: usize) -> [u8; N] { pub fn read_bytes<const N: usize>(&self, offset: usize) -> [u8; N] {
let data = unsafe { self.as_slice() }; let lua = self.0.lua.lock();
let data = self.as_slice(&lua);
let mut bytes = [0u8; N]; let mut bytes = [0u8; N];
bytes.copy_from_slice(&data[offset..offset + N]); bytes.copy_from_slice(&data[offset..offset + N]);
bytes bytes
@@ -46,21 +51,36 @@ impl Buffer {
/// Offset is 0-based. /// Offset is 0-based.
#[track_caller] #[track_caller]
pub fn write_bytes(&self, offset: usize, bytes: &[u8]) { pub fn write_bytes(&self, offset: usize, bytes: &[u8]) {
let data = unsafe { let lua = self.0.lua.lock();
let (buf, size) = self.as_raw_parts(); let data = self.as_slice_mut(&lua);
std::slice::from_raw_parts_mut(buf, size)
};
data[offset..offset + bytes.len()].copy_from_slice(bytes); data[offset..offset + bytes.len()].copy_from_slice(bytes);
} }
pub(crate) unsafe fn as_slice(&self) -> &[u8] { /// Returns an adaptor implementing [`io::Read`], [`io::Write`] and [`io::Seek`] over the
let (buf, size) = self.as_raw_parts(); /// buffer.
std::slice::from_raw_parts(buf, size) ///
/// Buffer operations are infallible, none of the read/write functions will return a Err.
pub fn cursor(self) -> impl io::Read + io::Write + io::Seek {
BufferCursor(self, 0)
}
pub(crate) fn as_slice(&self, lua: &RawLua) -> &[u8] {
unsafe {
let (buf, size) = self.as_raw_parts(lua);
std::slice::from_raw_parts(buf, size)
}
}
#[allow(clippy::mut_from_ref)]
fn as_slice_mut(&self, lua: &RawLua) -> &mut [u8] {
unsafe {
let (buf, size) = self.as_raw_parts(lua);
std::slice::from_raw_parts_mut(buf, size)
}
} }
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
unsafe fn as_raw_parts(&self) -> (*mut u8, usize) { unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) {
let lua = self.0.lua.lock();
let mut size = 0usize; let mut size = 0usize;
let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size); let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size);
mlua_assert!(!buf.is_null(), "invalid Luau buffer"); mlua_assert!(!buf.is_null(), "invalid Luau buffer");
@@ -68,15 +88,76 @@ impl Buffer {
} }
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
unsafe fn as_raw_parts(&self) -> (*mut u8, usize) { unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) {
unreachable!() unreachable!()
} }
} }
#[cfg(feature = "serialize")] struct BufferCursor(Buffer, usize);
impl io::Read for BufferCursor {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let lua = self.0.0.lua.lock();
let data = self.0.as_slice(&lua);
if self.1 == data.len() {
return Ok(0);
}
let len = buf.len().min(data.len() - self.1);
buf[..len].copy_from_slice(&data[self.1..self.1 + len]);
self.1 += len;
Ok(len)
}
}
impl io::Write for BufferCursor {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let lua = self.0.0.lua.lock();
let data = self.0.as_slice_mut(&lua);
if self.1 == data.len() {
return Ok(0);
}
let len = buf.len().min(data.len() - self.1);
data[self.1..self.1 + len].copy_from_slice(&buf[..len]);
self.1 += len;
Ok(len)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl io::Seek for BufferCursor {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
let lua = self.0.0.lua.lock();
let data = self.0.as_slice(&lua);
let new_offset = match pos {
io::SeekFrom::Start(offset) => offset as i64,
io::SeekFrom::End(offset) => data.len() as i64 + offset,
io::SeekFrom::Current(offset) => self.1 as i64 + offset,
};
if new_offset < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid seek to a negative position",
));
}
if new_offset as usize > data.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid seek to a position beyond the end of the buffer",
));
}
self.1 = new_offset as usize;
Ok(self.1 as u64)
}
}
#[cfg(feature = "serde")]
impl Serialize for Buffer { impl Serialize for Buffer {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> { fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
serializer.serialize_bytes(unsafe { self.as_slice() }) let lua = self.0.lua.lock();
serializer.serialize_bytes(self.as_slice(&lua))
} }
} }
+161 -91
View File
@@ -4,7 +4,6 @@ use std::ffi::CString;
use std::io::Result as IoResult; use std::io::Result as IoResult;
use std::panic::Location; use std::panic::Location;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::string::String as StdString;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::function::Function; use crate::function::Function;
@@ -20,7 +19,7 @@ pub trait AsChunk {
/// Returns optional chunk name /// Returns optional chunk name
/// ///
/// See [`Chunk::set_name`] for possible name prefixes. /// See [`Chunk::set_name`] for possible name prefixes.
fn name(&self) -> Option<StdString> { fn name(&self) -> Option<String> {
None None
} }
@@ -38,28 +37,28 @@ pub trait AsChunk {
} }
/// Returns chunk data (can be text or binary) /// Returns chunk data (can be text or binary)
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where where
Self: 'a; Self: 'a;
} }
impl AsChunk for &str { impl AsChunk for &str {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where where
Self: 'a, Self: 'a,
{ {
Ok(Cow::Borrowed(self.as_ref())) Ok(Cow::Borrowed(self.as_bytes()))
} }
} }
impl AsChunk for StdString { impl AsChunk for String {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> { fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Owned(self.into_bytes())) Ok(Cow::Owned(self.clone().into_bytes()))
} }
} }
impl AsChunk for &StdString { impl AsChunk for &String {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where where
Self: 'a, Self: 'a,
{ {
@@ -68,7 +67,7 @@ impl AsChunk for &StdString {
} }
impl AsChunk for &[u8] { impl AsChunk for &[u8] {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where where
Self: 'a, Self: 'a,
{ {
@@ -77,13 +76,13 @@ impl AsChunk for &[u8] {
} }
impl AsChunk for Vec<u8> { impl AsChunk for Vec<u8> {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> { fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Owned(self)) Ok(Cow::Owned(self.clone()))
} }
} }
impl AsChunk for &Vec<u8> { impl AsChunk for &Vec<u8> {
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where where
Self: 'a, Self: 'a,
{ {
@@ -92,30 +91,51 @@ impl AsChunk for &Vec<u8> {
} }
impl AsChunk for &Path { impl AsChunk for &Path {
fn name(&self) -> Option<StdString> { fn name(&self) -> Option<String> {
Some(format!("@{}", self.display())) Some(format!("@{}", self.display()))
} }
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> { fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
std::fs::read(self).map(Cow::Owned) std::fs::read(self).map(Cow::Owned)
} }
} }
impl AsChunk for PathBuf { impl AsChunk for PathBuf {
fn name(&self) -> Option<StdString> { fn name(&self) -> Option<String> {
Some(format!("@{}", self.display())) Some(format!("@{}", self.display()))
} }
fn source<'a>(self) -> IoResult<Cow<'a, [u8]>> { fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
std::fs::read(self).map(Cow::Owned) std::fs::read(self).map(Cow::Owned)
} }
} }
impl<C: AsChunk + ?Sized> AsChunk for Box<C> {
fn name(&self) -> Option<String> {
(**self).name()
}
fn environment(&self, lua: &Lua) -> Result<Option<Table>> {
(**self).environment(lua)
}
fn mode(&self) -> Option<ChunkMode> {
(**self).mode()
}
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
(**self).source()
}
}
/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks. /// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks.
#[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"] #[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"]
pub struct Chunk<'a> { pub struct Chunk<'a> {
pub(crate) lua: WeakLua, pub(crate) lua: WeakLua,
pub(crate) name: StdString, pub(crate) name: String,
pub(crate) env: Result<Option<Table>>, pub(crate) env: Result<Option<Table>>,
pub(crate) mode: Option<ChunkMode>, pub(crate) mode: Option<ChunkMode>,
pub(crate) source: IoResult<Cow<'a, [u8]>>, pub(crate) source: IoResult<Cow<'a, [u8]>>,
@@ -142,15 +162,36 @@ pub enum CompileConstant {
String(String), String(String),
} }
#[cfg(feature = "luau")] #[cfg(any(feature = "luau", doc))]
impl From<&'static str> for CompileConstant { impl From<bool> for CompileConstant {
fn from(s: &'static str) -> Self { fn from(b: bool) -> Self {
CompileConstant::String(s.to_string()) CompileConstant::Boolean(b)
} }
} }
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
type LibraryMemberConstantMap = std::sync::Arc<HashMap<(String, String), CompileConstant>>; impl From<crate::Number> for CompileConstant {
fn from(n: crate::Number) -> Self {
CompileConstant::Number(n)
}
}
#[cfg(any(feature = "luau", doc))]
impl From<crate::Vector> for CompileConstant {
fn from(v: crate::Vector) -> Self {
CompileConstant::Vector(v)
}
}
#[cfg(any(feature = "luau", doc))]
impl From<&str> for CompileConstant {
fn from(s: &str) -> Self {
CompileConstant::String(s.to_owned())
}
}
#[cfg(any(feature = "luau", doc))]
type LibraryMemberConstantMap = HashMap<(String, String), CompileConstant>;
/// Luau compiler /// Luau compiler
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
@@ -246,20 +287,23 @@ impl Compiler {
self self
} }
#[doc(hidden)] /// Sets alternative global builtin to construct vectors, in addition to default builtin
#[must_use] /// `vector.create`.
pub fn set_vector_lib(mut self, lib: impl Into<String>) -> Self { ///
self.vector_lib = Some(lib.into()); /// To set the library and method name, use the `lib.ctor` format.
self
}
#[doc(hidden)] #[doc(hidden)]
#[must_use] #[must_use]
pub fn set_vector_ctor(mut self, ctor: impl Into<String>) -> Self { pub fn set_vector_ctor(mut self, ctor: impl Into<String>) -> Self {
self.vector_ctor = Some(ctor.into()); let ctor = ctor.into();
let lib_ctor = ctor.split_once('.');
self.vector_lib = lib_ctor.as_ref().map(|&(lib, _)| lib.to_owned());
self.vector_ctor = (lib_ctor.as_ref())
.map(|&(_, ctor)| ctor.to_owned())
.or(Some(ctor));
self self
} }
/// Sets alternative vector type name for type tables, in addition to default type `vector`.
#[doc(hidden)] #[doc(hidden)]
#[must_use] #[must_use]
pub fn set_vector_type(mut self, r#type: impl Into<String>) -> Self { pub fn set_vector_type(mut self, r#type: impl Into<String>) -> Self {
@@ -267,49 +311,75 @@ impl Compiler {
self self
} }
/// Adds a mutable global.
///
/// It disables the import optimization for fields accessed through it.
#[must_use]
pub fn add_mutable_global(mut self, global: impl Into<String>) -> Self {
self.mutable_globals.push(global.into());
self
}
/// Sets a list of globals that are mutable. /// Sets a list of globals that are mutable.
/// ///
/// It disables the import optimization for fields accessed through these. /// It disables the import optimization for fields accessed through these.
#[must_use] #[must_use]
pub fn set_mutable_globals<S: Into<String>>(mut self, globals: Vec<S>) -> Self { pub fn set_mutable_globals<S: Into<String>>(mut self, globals: impl IntoIterator<Item = S>) -> Self {
self.mutable_globals = globals.into_iter().map(|s| s.into()).collect(); self.mutable_globals = globals.into_iter().map(|s| s.into()).collect();
self self
} }
/// Adds a userdata type to the list that will be included in the type information.
#[must_use]
pub fn add_userdata_type(mut self, r#type: impl Into<String>) -> Self {
self.userdata_types.push(r#type.into());
self
}
/// Sets a list of userdata types that will be included in the type information. /// Sets a list of userdata types that will be included in the type information.
#[must_use] #[must_use]
pub fn set_userdata_types<S: Into<String>>(mut self, types: Vec<S>) -> Self { pub fn set_userdata_types<S: Into<String>>(mut self, types: impl IntoIterator<Item = S>) -> Self {
self.userdata_types = types.into_iter().map(|s| s.into()).collect(); self.userdata_types = types.into_iter().map(|s| s.into()).collect();
self self
} }
/// Sets constants for known library members. /// Adds a constant for a known library member.
/// ///
/// The constants are used by the compiler to optimize the generated bytecode. /// 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. /// 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 /// The `name` is a string in the format `lib.member`, where `lib` is the library name
/// third is the constant value. /// and `member` is the member (constant) name.
#[must_use] #[must_use]
pub fn set_library_constants<L, M>(mut self, constants: Vec<(L, M, CompileConstant)>) -> Self pub fn add_library_constant(
where mut self,
L: Into<String>, name: impl AsRef<str>,
M: Into<String>, r#const: impl Into<CompileConstant>,
{ ) -> Self {
let map = constants let Some((lib, member)) = name.as_ref().split_once('.') else {
.into_iter() return self;
.map(|(lib, member, cons)| ((lib.into(), member.into()), cons)) };
.collect::<HashMap<_, _>>(); let (lib, member) = (lib.to_owned(), member.to_owned());
self.library_constants = Some(std::sync::Arc::new(map));
self.libraries_with_known_members = (self.library_constants.clone()) if !self.libraries_with_known_members.contains(&lib) {
.map(|map| map.keys().map(|(lib, _)| lib.clone()).collect()) self.libraries_with_known_members.push(lib.clone());
.unwrap_or_default(); }
self.library_constants
.get_or_insert_default()
.insert((lib, member), r#const.into());
self
}
/// Adds a builtin that should be disabled.
#[must_use]
pub fn add_disabled_builtin(mut self, builtin: impl Into<String>) -> Self {
self.disabled_builtins.push(builtin.into());
self self
} }
/// Sets a list of builtins that should be disabled. /// Sets a list of builtins that should be disabled.
#[must_use] #[must_use]
pub fn set_disabled_builtins<S: Into<String>>(mut self, builtins: Vec<S>) -> Self { pub fn set_disabled_builtins<S: Into<String>>(mut self, builtins: impl IntoIterator<Item = S>) -> Self {
self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect(); self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect();
self self
} }
@@ -403,11 +473,11 @@ impl Compiler {
options.mutableGlobals = mutable_globals_ptr; options.mutableGlobals = mutable_globals_ptr;
options.userdataTypes = userdata_types_ptr; options.userdataTypes = userdata_types_ptr;
options.librariesWithKnownMembers = libraries_with_known_members_ptr; options.librariesWithKnownMembers = libraries_with_known_members_ptr;
if let Some(map) = self.library_constants.as_ref() { if let Some(map) = self.library_constants.as_ref()
if !self.libraries_with_known_members.is_empty() { && !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); LIBRARY_MEMBER_CONSTANT_MAP.with_borrow_mut(|gmap| *gmap = map.clone());
} options.libraryMemberConstantCallback = Some(library_member_constant_callback);
} }
options.disabledBuiltins = disabled_builtins_ptr; options.disabledBuiltins = disabled_builtins_ptr;
ffi::luau_compile(source.as_ref(), options) ffi::luau_compile(source.as_ref(), options)
@@ -416,7 +486,7 @@ impl Compiler {
if bytecode.first() == Some(&0) { if bytecode.first() == Some(&0) {
// The rest of the bytecode is the error message starting with `:` // The rest of the bytecode is the error message starting with `:`
// See https://github.com/luau-lang/luau/blob/0.640/Compiler/src/Compiler.cpp#L4336 // See https://github.com/luau-lang/luau/blob/0.640/Compiler/src/Compiler.cpp#L4336
let message = String::from_utf8_lossy(&bytecode[2..]).to_string(); let message = String::from_utf8_lossy(&bytecode[2..]).into_owned();
return Err(Error::SyntaxError { return Err(Error::SyntaxError {
incomplete_input: message.ends_with("<eof>"), incomplete_input: message.ends_with("<eof>"),
message, message,
@@ -588,19 +658,19 @@ impl Chunk<'_> {
/// ///
/// It does nothing if the chunk is already binary or invalid. /// It does nothing if the chunk is already binary or invalid.
fn compile(&mut self) { fn compile(&mut self) {
if let Ok(ref source) = self.source { if let Ok(ref source) = self.source
if self.detect_mode() == ChunkMode::Text { && self.detect_mode() == ChunkMode::Text
#[cfg(feature = "luau")] {
if let Ok(data) = self.compiler.get_or_insert_with(Default::default).compile(source) { #[cfg(feature = "luau")]
self.source = Ok(Cow::Owned(data)); if let Ok(data) = self.compiler.get_or_insert_default().compile(source) {
self.mode = Some(ChunkMode::Binary); self.source = Ok(Cow::Owned(data));
} self.mode = Some(ChunkMode::Binary);
#[cfg(not(feature = "luau"))] }
if let Ok(func) = self.lua.lock().load_chunk(None, None, None, source.as_ref()) { #[cfg(not(feature = "luau"))]
let data = func.dump(false); if let Ok(func) = self.lua.lock().load_chunk(None, None, None, source.as_ref()) {
self.source = Ok(Cow::Owned(data)); let data = func.dump(false);
self.mode = Some(ChunkMode::Binary); self.source = Ok(Cow::Owned(data));
} self.mode = Some(ChunkMode::Binary);
} }
} }
} }
@@ -613,33 +683,33 @@ impl Chunk<'_> {
// Try to fetch compiled chunk from cache // Try to fetch compiled chunk from cache
let mut text_source = None; let mut text_source = None;
if let Ok(ref source) = self.source { if let Ok(ref source) = self.source
if self.detect_mode() == ChunkMode::Text { && self.detect_mode() == ChunkMode::Text
let lua = self.lua.lock(); {
if let Some(cache) = lua.app_data_ref_unguarded::<ChunksCache>() { let lua = self.lua.lock();
if let Some(data) = cache.0.get(source.as_ref()) { if let Some(cache) = lua.priv_app_data_ref::<ChunksCache>()
self.source = Ok(Cow::Owned(data.clone())); && let Some(data) = cache.0.get(source.as_ref())
self.mode = Some(ChunkMode::Binary); {
return self; self.source = Ok(Cow::Owned(data.clone()));
} self.mode = Some(ChunkMode::Binary);
} return self;
text_source = Some(source.as_ref().to_vec());
} }
text_source = Some(source.as_ref().to_vec());
} }
// Compile and cache the chunk // Compile and cache the chunk
if let Some(text_source) = text_source { if let Some(text_source) = text_source {
self.compile(); self.compile();
if let Ok(ref binary_source) = self.source { if let Ok(ref binary_source) = self.source
if self.detect_mode() == ChunkMode::Binary { && self.detect_mode() == ChunkMode::Binary
let lua = self.lua.lock(); {
if let Some(mut cache) = lua.app_data_mut_unguarded::<ChunksCache>() { let lua = self.lua.lock();
cache.0.insert(text_source, binary_source.to_vec()); if let Some(mut cache) = lua.priv_app_data_mut::<ChunksCache>() {
} else { cache.0.insert(text_source, binary_source.to_vec());
let mut cache = ChunksCache(HashMap::new()); } else {
cache.0.insert(text_source, binary_source.to_vec()); let mut cache = ChunksCache(HashMap::new());
let _ = lua.try_set_app_data(cache); cache.0.insert(text_source, binary_source.to_vec());
}; lua.set_priv_app_data(cache);
} }
} }
} }
+139 -196
View File
@@ -4,16 +4,15 @@ use std::ffi::{CStr, CString, OsStr, OsString};
use std::hash::{BuildHasher, Hash}; use std::hash::{BuildHasher, Hash};
use std::os::raw::c_int; use std::os::raw::c_int;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::string::String as StdString;
use std::{mem, slice, str}; use std::{mem, slice, str};
use bstr::{BStr, BString, ByteSlice, ByteVec}; use bstr::{BStr, BString, ByteVec};
use num_traits::cast; use num_traits::cast;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::function::Function; use crate::function::Function;
use crate::state::{Lua, RawLua}; use crate::state::{Lua, RawLua};
use crate::string::{BorrowedBytes, BorrowedStr, String}; use crate::string::{BorrowedBytes, BorrowedStr, LuaString};
use crate::table::Table; use crate::table::Table;
use crate::thread::Thread; use crate::thread::Thread;
use crate::traits::{FromLua, IntoLua, ShortTypeName as _}; use crate::traits::{FromLua, IntoLua, ShortTypeName as _};
@@ -47,14 +46,14 @@ impl FromLua for Value {
} }
} }
impl IntoLua for String { impl IntoLua for LuaString {
#[inline] #[inline]
fn into_lua(self, _: &Lua) -> Result<Value> { fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self)) Ok(Value::String(self))
} }
} }
impl IntoLua for &String { impl IntoLua for &LuaString {
#[inline] #[inline]
fn into_lua(self, _: &Lua) -> Result<Value> { fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.clone())) Ok(Value::String(self.clone()))
@@ -67,16 +66,12 @@ impl IntoLua for &String {
} }
} }
impl FromLua for String { impl FromLua for LuaString {
#[inline] #[inline]
fn from_lua(value: Value, lua: &Lua) -> Result<String> { fn from_lua(value: Value, lua: &Lua) -> Result<LuaString> {
let ty = value.type_name(); let ty = value.type_name();
lua.coerce_string(value)? lua.coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError { .ok_or_else(|| Error::from_lua_conversion(ty, "string", "expected string or number".to_string()))
from: ty,
to: "string".to_string(),
message: Some("expected string or number".to_string()),
})
} }
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> { unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
@@ -84,7 +79,7 @@ impl FromLua for String {
let type_id = ffi::lua_type(state, idx); let type_id = ffi::lua_type(state, idx);
if type_id == ffi::LUA_TSTRING { if type_id == ffi::LUA_TSTRING {
ffi::lua_xpush(state, lua.ref_thread(), idx); ffi::lua_xpush(state, lua.ref_thread(), idx);
return Ok(String(lua.pop_ref_thread())); return Ok(LuaString(lua.pop_ref_thread()));
} }
// Fallback to default // Fallback to default
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
@@ -119,7 +114,7 @@ impl IntoLua for &BorrowedStr<'_> {
impl FromLua for BorrowedStr<'_> { impl FromLua for BorrowedStr<'_> {
fn from_lua(value: Value, lua: &Lua) -> Result<Self> { fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let s = String::from_lua(value, lua)?; let s = LuaString::from_lua(value, lua)?;
let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?; let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?;
let buf = unsafe { mem::transmute::<&str, &'static str>(buf) }; let buf = unsafe { mem::transmute::<&str, &'static str>(buf) };
let borrow = Cow::Owned(s); let borrow = Cow::Owned(s);
@@ -127,7 +122,7 @@ impl FromLua for BorrowedStr<'_> {
} }
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> { unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let s = String::from_stack(idx, lua)?; let s = LuaString::from_stack(idx, lua)?;
let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?; let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?;
let buf = unsafe { mem::transmute::<&str, &'static str>(buf) }; let buf = unsafe { mem::transmute::<&str, &'static str>(buf) };
let borrow = Cow::Owned(s); let borrow = Cow::Owned(s);
@@ -163,7 +158,7 @@ impl IntoLua for &BorrowedBytes<'_> {
impl FromLua for BorrowedBytes<'_> { impl FromLua for BorrowedBytes<'_> {
fn from_lua(value: Value, lua: &Lua) -> Result<Self> { fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let s = String::from_lua(value, lua)?; let s = LuaString::from_lua(value, lua)?;
let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s); let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s);
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) }; let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
let borrow = Cow::Owned(s); let borrow = Cow::Owned(s);
@@ -171,7 +166,7 @@ impl FromLua for BorrowedBytes<'_> {
} }
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> { unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let s = String::from_stack(idx, lua)?; let s = LuaString::from_stack(idx, lua)?;
let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s); let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s);
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) }; let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
let borrow = Cow::Owned(s); let borrow = Cow::Owned(s);
@@ -204,11 +199,7 @@ impl FromLua for Table {
fn from_lua(value: Value, _: &Lua) -> Result<Table> { fn from_lua(value: Value, _: &Lua) -> Result<Table> {
match value { match value {
Value::Table(table) => Ok(table), Value::Table(table) => Ok(table),
_ => Err(Error::FromLuaConversionError { _ => Err(Error::from_lua_conversion(value.type_name(), "table", None)),
from: value.type_name(),
to: "table".to_string(),
message: None,
}),
} }
} }
} }
@@ -238,11 +229,7 @@ impl FromLua for Function {
fn from_lua(value: Value, _: &Lua) -> Result<Function> { fn from_lua(value: Value, _: &Lua) -> Result<Function> {
match value { match value {
Value::Function(table) => Ok(table), Value::Function(table) => Ok(table),
_ => Err(Error::FromLuaConversionError { _ => Err(Error::from_lua_conversion(value.type_name(), "function", None)),
from: value.type_name(),
to: "function".to_string(),
message: None,
}),
} }
} }
} }
@@ -272,11 +259,7 @@ impl FromLua for Thread {
fn from_lua(value: Value, _: &Lua) -> Result<Thread> { fn from_lua(value: Value, _: &Lua) -> Result<Thread> {
match value { match value {
Value::Thread(t) => Ok(t), Value::Thread(t) => Ok(t),
_ => Err(Error::FromLuaConversionError { _ => Err(Error::from_lua_conversion(value.type_name(), "thread", None)),
from: value.type_name(),
to: "thread".to_string(),
message: None,
}),
} }
} }
} }
@@ -306,11 +289,7 @@ impl FromLua for AnyUserData {
fn from_lua(value: Value, _: &Lua) -> Result<AnyUserData> { fn from_lua(value: Value, _: &Lua) -> Result<AnyUserData> {
match value { match value {
Value::UserData(ud) => Ok(ud), Value::UserData(ud) => Ok(ud),
_ => Err(Error::FromLuaConversionError { _ => Err(Error::from_lua_conversion(value.type_name(), "userdata", None)),
from: value.type_name(),
to: "userdata".to_string(),
message: None,
}),
} }
} }
} }
@@ -428,11 +407,11 @@ impl FromLua for LightUserData {
fn from_lua(value: Value, _: &Lua) -> Result<Self> { fn from_lua(value: Value, _: &Lua) -> Result<Self> {
match value { match value {
Value::LightUserData(ud) => Ok(ud), Value::LightUserData(ud) => Ok(ud),
_ => Err(Error::FromLuaConversionError { _ => Err(Error::from_lua_conversion(
from: value.type_name(), value.type_name(),
to: "lightuserdata".to_string(), "lightuserdata",
message: None, None,
}), )),
} }
} }
} }
@@ -451,11 +430,7 @@ impl FromLua for crate::Vector {
fn from_lua(value: Value, _: &Lua) -> Result<Self> { fn from_lua(value: Value, _: &Lua) -> Result<Self> {
match value { match value {
Value::Vector(v) => Ok(v), Value::Vector(v) => Ok(v),
_ => Err(Error::FromLuaConversionError { _ => Err(Error::from_lua_conversion(value.type_name(), "vector", None)),
from: value.type_name(),
to: "vector".to_string(),
message: None,
}),
} }
} }
} }
@@ -488,37 +463,41 @@ impl FromLua for crate::Buffer {
fn from_lua(value: Value, _: &Lua) -> Result<Self> { fn from_lua(value: Value, _: &Lua) -> Result<Self> {
match value { match value {
Value::Buffer(buf) => Ok(buf), Value::Buffer(buf) => Ok(buf),
_ => Err(Error::FromLuaConversionError { _ => Err(Error::from_lua_conversion(value.type_name(), "buffer", None)),
from: value.type_name(),
to: "buffer".to_string(),
message: None,
}),
} }
} }
} }
impl IntoLua for StdString { impl IntoLua for String {
#[inline] #[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> { fn into_lua(self, lua: &Lua) -> Result<Value> {
#[cfg(feature = "lua55")]
if true {
return Ok(Value::String(lua.create_external_string(self)?));
}
Ok(Value::String(lua.create_string(self)?)) Ok(Value::String(lua.create_string(self)?))
} }
#[inline] #[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> { unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
#[cfg(feature = "lua55")]
if lua.unlikely_memory_error() {
return crate::util::push_external_string(lua.state(), self.into(), false);
}
push_bytes_into_stack(self, lua) push_bytes_into_stack(self, lua)
} }
} }
impl FromLua for StdString { impl FromLua for String {
#[inline] #[inline]
fn from_lua(value: Value, lua: &Lua) -> Result<Self> { fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let ty = value.type_name(); let ty = value.type_name();
Ok(lua Ok(lua
.coerce_string(value)? .coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError { .ok_or_else(|| {
from: ty, Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string())
to: Self::type_name(),
message: Some("expected string or number".to_string()),
})? })?
.to_str()? .to_str()?
.to_owned()) .to_owned())
@@ -534,11 +513,7 @@ impl FromLua for StdString {
let bytes = slice::from_raw_parts(data as *const u8, size); let bytes = slice::from_raw_parts(data as *const u8, size);
return str::from_utf8(bytes) return str::from_utf8(bytes)
.map(|s| s.to_owned()) .map(|s| s.to_owned())
.map_err(|e| Error::FromLuaConversionError { .map_err(|e| Error::from_lua_conversion("string", Self::type_name(), e.to_string()));
from: "string",
to: Self::type_name(),
message: Some(e.to_string()),
});
} }
// Fallback to default // Fallback to default
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua()) Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
@@ -577,10 +552,8 @@ impl FromLua for Box<str> {
let ty = value.type_name(); let ty = value.type_name();
Ok(lua Ok(lua
.coerce_string(value)? .coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError { .ok_or_else(|| {
from: ty, Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string())
to: Self::type_name(),
message: Some("expected string or number".to_string()),
})? })?
.to_str()? .to_str()?
.to_owned() .to_owned()
@@ -591,6 +564,11 @@ impl FromLua for Box<str> {
impl IntoLua for CString { impl IntoLua for CString {
#[inline] #[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> { fn into_lua(self, lua: &Lua) -> Result<Value> {
#[cfg(feature = "lua55")]
if true {
return Ok(Value::String(lua.create_external_string(self)?));
}
Ok(Value::String(lua.create_string(self.as_bytes())?)) Ok(Value::String(lua.create_string(self.as_bytes())?))
} }
} }
@@ -599,21 +577,12 @@ impl FromLua for CString {
#[inline] #[inline]
fn from_lua(value: Value, lua: &Lua) -> Result<Self> { fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let ty = value.type_name(); let ty = value.type_name();
let string = lua let string = lua.coerce_string(value)?.ok_or_else(|| {
.coerce_string(value)? Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string())
.ok_or_else(|| Error::FromLuaConversionError { })?;
from: ty,
to: Self::type_name(),
message: Some("expected string or number".to_string()),
})?;
match CStr::from_bytes_with_nul(&string.as_bytes_with_nul()) { match CStr::from_bytes_with_nul(&string.as_bytes_with_nul()) {
Ok(s) => Ok(s.into()), Ok(s) => Ok(s.into()),
Err(_) => Err(Error::FromLuaConversionError { Err(err) => Err(Error::from_lua_conversion(ty, Self::type_name(), err.to_string())),
from: ty,
to: Self::type_name(),
message: Some("invalid C-style string".to_string()),
}),
} }
} }
} }
@@ -635,6 +604,11 @@ impl IntoLua for Cow<'_, CStr> {
impl IntoLua for BString { impl IntoLua for BString {
#[inline] #[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> { fn into_lua(self, lua: &Lua) -> Result<Value> {
#[cfg(feature = "lua55")]
if true {
return Ok(Value::String(lua.create_external_string(self)?));
}
Ok(Value::String(lua.create_string(self)?)) Ok(Value::String(lua.create_string(self)?))
} }
} }
@@ -645,13 +619,11 @@ impl FromLua for BString {
match value { match value {
Value::String(s) => Ok((*s.as_bytes()).into()), Value::String(s) => Ok((*s.as_bytes()).into()),
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
Value::Buffer(buf) => unsafe { Ok(buf.as_slice().into()) }, Value::Buffer(buf) => Ok(buf.to_vec().into()),
_ => Ok((*lua _ => Ok((*lua
.coerce_string(value)? .coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError { .ok_or_else(|| {
from: ty, Error::from_lua_conversion(ty, Self::type_name(), "expected string or number".to_string())
to: Self::type_name(),
message: Some("expected string or number".to_string()),
})? })?
.as_bytes()) .as_bytes())
.into()), .into()),
@@ -702,23 +674,22 @@ impl FromLua for OsString {
let bs = BString::from_lua(value, lua)?; let bs = BString::from_lua(value, lua)?;
Vec::from(bs) Vec::from(bs)
.into_os_string() .into_os_string()
.map_err(|err| Error::FromLuaConversionError { .map_err(|err| Error::from_lua_conversion(ty, "OsString", err.to_string()))
from: ty,
to: "OsString".into(),
message: Some(err.to_string()),
})
} }
} }
impl IntoLua for &OsStr { impl IntoLua for &OsStr {
#[cfg(unix)]
#[inline] #[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> { fn into_lua(self, lua: &Lua) -> Result<Value> {
let s = <[u8]>::from_os_str(self).ok_or_else(|| Error::ToLuaConversionError { use std::os::unix::ffi::OsStrExt;
from: "OsStr".into(), Ok(Value::String(lua.create_string(self.as_bytes())?))
to: "string", }
message: Some("invalid utf-8 encoding".into()),
})?; #[cfg(not(unix))]
Ok(Value::String(lua.create_string(s)?)) #[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
self.display().to_string().into_lua(lua)
} }
} }
@@ -756,34 +727,25 @@ impl FromLua for char {
fn from_lua(value: Value, _lua: &Lua) -> Result<Self> { fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
let ty = value.type_name(); let ty = value.type_name();
match value { match value {
Value::Integer(i) => { Value::Integer(i) => cast(i).and_then(char::from_u32).ok_or_else(|| {
cast(i) let msg = "integer out of range when converting to char";
.and_then(char::from_u32) Error::from_lua_conversion(ty, "char", msg.to_string())
.ok_or_else(|| Error::FromLuaConversionError { }),
from: ty,
to: "char".to_string(),
message: Some("integer out of range when converting to char".to_string()),
})
}
Value::String(s) => { Value::String(s) => {
let str = s.to_str()?; let str = s.to_str()?;
let mut str_iter = str.chars(); let mut str_iter = str.chars();
match (str_iter.next(), str_iter.next()) { match (str_iter.next(), str_iter.next()) {
(Some(char), None) => Ok(char), (Some(char), None) => Ok(char),
_ => Err(Error::FromLuaConversionError { _ => {
from: ty, let msg = "expected string to have exactly one char when converting to char";
to: "char".to_string(), Err(Error::from_lua_conversion(ty, "char", msg.to_string()))
message: Some( }
"expected string to have exactly one char when converting to char".to_string(),
),
}),
} }
} }
_ => Err(Error::FromLuaConversionError { _ => {
from: ty, let msg = "expected string or integer";
to: Self::type_name(), Err(Error::from_lua_conversion(ty, Self::type_name(), msg.to_string()))
message: Some("expected string or integer".to_string()), }
}),
} }
} }
} }
@@ -834,24 +796,14 @@ macro_rules! lua_convert_int {
if let Some(i) = lua.coerce_integer(value.clone())? { if let Some(i) = lua.coerce_integer(value.clone())? {
cast(i) cast(i)
} else { } else {
cast( cast(lua.coerce_number(value)?.ok_or_else(|| {
lua.coerce_number(value)? let msg = "expected number or string coercible to number";
.ok_or_else(|| Error::FromLuaConversionError { Error::from_lua_conversion(ty, stringify!($x), msg.to_string())
from: ty, })?)
to: stringify!($x).to_string(),
message: Some(
"expected number or string coercible to number".to_string(),
),
})?,
)
} }
} }
}) })
.ok_or_else(|| Error::FromLuaConversionError { .ok_or_else(|| Error::from_lua_conversion(ty, stringify!($x), "out of range".to_string()))
from: ty,
to: stringify!($x).to_string(),
message: Some("out of range".to_owned()),
})
} }
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> { unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
@@ -861,10 +813,8 @@ macro_rules! lua_convert_int {
let mut ok = 0; let mut ok = 0;
let i = ffi::lua_tointegerx(state, idx, &mut ok); let i = ffi::lua_tointegerx(state, idx, &mut ok);
if ok != 0 { if ok != 0 {
return cast(i).ok_or_else(|| Error::FromLuaConversionError { return cast(i).ok_or_else(|| {
from: "integer", Error::from_lua_conversion("integer", stringify!($x), "out of range".to_string())
to: stringify!($x).to_string(),
message: Some("out of range".to_owned()),
}); });
} }
} }
@@ -901,13 +851,10 @@ macro_rules! lua_convert_float {
#[inline] #[inline]
fn from_lua(value: Value, lua: &Lua) -> Result<Self> { fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let ty = value.type_name(); let ty = value.type_name();
lua.coerce_number(value)? lua.coerce_number(value)?.map(|n| n as $x).ok_or_else(|| {
.map(|n| n as $x) let msg = "expected number or string coercible to number";
.ok_or_else(|| Error::FromLuaConversionError { Error::from_lua_conversion(ty, stringify!($x), msg.to_string())
from: ty, })
to: stringify!($x).to_string(),
message: Some("expected number or string coercible to number".to_string()),
})
} }
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> { unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
@@ -967,18 +914,16 @@ where
}, },
Value::Table(table) => { Value::Table(table) => {
let vec = table.sequence_values().collect::<Result<Vec<_>>>()?; let vec = table.sequence_values().collect::<Result<Vec<_>>>()?;
vec.try_into() vec.try_into().map_err(|vec: Vec<T>| {
.map_err(|vec: Vec<T>| Error::FromLuaConversionError { let msg = format!("expected table of length {N}, got {}", vec.len());
from: "table", Error::from_lua_conversion("table", Self::type_name(), msg)
to: Self::type_name(), })
message: Some(format!("expected table of length {N}, got {}", vec.len())), }
}) _ => {
let msg = format!("expected table of length {N}");
let err = Error::from_lua_conversion(value.type_name(), Self::type_name(), msg.to_string());
Err(err)
} }
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: Self::type_name(),
message: Some("expected table".to_string()),
}),
} }
} }
} }
@@ -1009,11 +954,11 @@ impl<T: FromLua> FromLua for Vec<T> {
fn from_lua(value: Value, _lua: &Lua) -> Result<Self> { fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
match value { match value {
Value::Table(table) => table.sequence_values().collect(), Value::Table(table) => table.sequence_values().collect(),
_ => Err(Error::FromLuaConversionError { _ => Err(Error::from_lua_conversion(
from: value.type_name(), value.type_name(),
to: Self::type_name(), Self::type_name(),
message: Some("expected table".to_string()), "expected table".to_string(),
}), )),
} }
} }
} }
@@ -1028,14 +973,13 @@ impl<K: Eq + Hash + IntoLua, V: IntoLua, S: BuildHasher> IntoLua for HashMap<K,
impl<K: Eq + Hash + FromLua, V: FromLua, S: BuildHasher + Default> FromLua for HashMap<K, V, S> { impl<K: Eq + Hash + FromLua, V: FromLua, S: BuildHasher + Default> FromLua for HashMap<K, V, S> {
#[inline] #[inline]
fn from_lua(value: Value, _: &Lua) -> Result<Self> { fn from_lua(value: Value, _: &Lua) -> Result<Self> {
if let Value::Table(table) = value { match value {
table.pairs().collect() Value::Table(table) => table.pairs().collect(),
} else { _ => Err(Error::from_lua_conversion(
Err(Error::FromLuaConversionError { value.type_name(),
from: value.type_name(), Self::type_name(),
to: Self::type_name(), "expected table".to_string(),
message: Some("expected table".to_string()), )),
})
} }
} }
} }
@@ -1050,14 +994,13 @@ impl<K: Ord + IntoLua, V: IntoLua> IntoLua for BTreeMap<K, V> {
impl<K: Ord + FromLua, V: FromLua> FromLua for BTreeMap<K, V> { impl<K: Ord + FromLua, V: FromLua> FromLua for BTreeMap<K, V> {
#[inline] #[inline]
fn from_lua(value: Value, _: &Lua) -> Result<Self> { fn from_lua(value: Value, _: &Lua) -> Result<Self> {
if let Value::Table(table) = value { match value {
table.pairs().collect() Value::Table(table) => table.pairs().collect(),
} else { _ => Err(Error::from_lua_conversion(
Err(Error::FromLuaConversionError { value.type_name(),
from: value.type_name(), Self::type_name(),
to: Self::type_name(), "expected table".to_string(),
message: Some("expected table".to_string()), )),
})
} }
} }
} }
@@ -1077,11 +1020,11 @@ impl<T: Eq + Hash + FromLua, S: BuildHasher + Default> FromLua for HashSet<T, S>
match value { match value {
Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(), Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(),
Value::Table(table) => table.pairs::<T, Value>().map(|res| res.map(|(k, _)| k)).collect(), Value::Table(table) => table.pairs::<T, Value>().map(|res| res.map(|(k, _)| k)).collect(),
_ => Err(Error::FromLuaConversionError { _ => Err(Error::from_lua_conversion(
from: value.type_name(), value.type_name(),
to: Self::type_name(), Self::type_name(),
message: Some("expected table".to_string()), "expected table".to_string(),
}), )),
} }
} }
} }
@@ -1101,11 +1044,11 @@ impl<T: Ord + FromLua> FromLua for BTreeSet<T> {
match value { match value {
Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(), Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(),
Value::Table(table) => table.pairs::<T, Value>().map(|res| res.map(|(k, _)| k)).collect(), Value::Table(table) => table.pairs::<T, Value>().map(|res| res.map(|(k, _)| k)).collect(),
_ => Err(Error::FromLuaConversionError { _ => Err(Error::from_lua_conversion(
from: value.type_name(), value.type_name(),
to: Self::type_name(), Self::type_name(),
message: Some("expected table".to_string()), "expected table".to_string(),
}), )),
} }
} }
} }
@@ -1175,11 +1118,11 @@ impl<L: FromLua, R: FromLua> FromLua for Either<L, R> {
// Try the right type // Try the right type
Err(_) => match R::from_lua(value, lua).map(Either::Right) { Err(_) => match R::from_lua(value, lua).map(Either::Right) {
Ok(r) => Ok(r), Ok(r) => Ok(r),
Err(_) => Err(Error::FromLuaConversionError { Err(_) => Err(Error::from_lua_conversion(
from: value_type_name, value_type_name,
to: Self::type_name(), Self::type_name(),
message: None, None,
}), )),
}, },
} }
} }
@@ -1191,12 +1134,12 @@ impl<L: FromLua, R: FromLua> FromLua for Either<L, R> {
Err(_) => match R::from_stack(idx, lua).map(Either::Right) { Err(_) => match R::from_stack(idx, lua).map(Either::Right) {
Ok(r) => Ok(r), Ok(r) => Ok(r),
Err(_) => { Err(_) => {
let value_type_name = CStr::from_ptr(ffi::luaL_typename(lua.state(), idx)); let state = lua.state();
Err(Error::FromLuaConversionError { let from_type_name = CStr::from_ptr(ffi::lua_typename(state, ffi::lua_type(state, idx)))
from: value_type_name.to_str().unwrap(), .to_str()
to: Self::type_name(), .unwrap_or("unknown");
message: None, let err = Error::from_lua_conversion(from_type_name, Self::type_name(), None);
}) Err(err)
} }
}, },
} }
+109 -119
View File
@@ -1,66 +1,39 @@
//! Lua debugging interface.
//!
//! This module provides access to the Lua debug interface, allowing inspection of the call stack,
//! and function information. The main types are [`Debug`] for accessing debug information and
//! [`HookTriggers`] for configuring debug hooks.
use std::borrow::Cow; use std::borrow::Cow;
use std::cell::UnsafeCell;
use std::ops::Deref;
#[cfg(not(feature = "luau"))]
use std::ops::{BitOr, BitOrAssign};
use std::os::raw::c_int; use std::os::raw::c_int;
use ffi::lua_Debug; use ffi::{lua_Debug, lua_State};
use crate::function::Function;
use crate::state::RawLua; use crate::state::RawLua;
use crate::types::ReentrantMutexGuard; use crate::util::{StackGuard, assert_stack, linenumber_to_usize, ptr_to_lossy_str, ptr_to_str};
use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str};
/// Contains information about currently executing Lua code. /// Contains information about currently executing Lua code.
/// ///
/// The `Debug` structure is provided as a parameter to the hook function set with /// You may call the methods on this structure to retrieve information about the Lua code executing
/// [`Lua::set_hook`]. You may call the methods on this structure to retrieve information about the /// at the specific level. Further information can be found in the Lua [documentation].
/// Lua code executing at the time that the hook function was called. Further information can be
/// found in the Lua [documentation].
/// ///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#lua_Debug /// [documentation]: https://www.lua.org/manual/5.4/manual.html#lua_Debug
/// [`Lua::set_hook`]: crate::Lua::set_hook
pub struct Debug<'a> { pub struct Debug<'a> {
lua: EitherLua<'a>, state: *mut lua_State,
ar: ActivationRecord, lua: &'a RawLua,
#[cfg(feature = "luau")] #[cfg_attr(not(feature = "luau"), allow(unused))]
level: c_int, level: c_int,
} ar: *mut lua_Debug,
enum EitherLua<'a> {
Owned(ReentrantMutexGuard<'a, RawLua>),
#[cfg(not(feature = "luau"))]
Borrowed(&'a RawLua),
}
impl Deref for EitherLua<'_> {
type Target = RawLua;
fn deref(&self) -> &Self::Target {
match self {
EitherLua::Owned(guard) => guard,
#[cfg(not(feature = "luau"))]
EitherLua::Borrowed(lua) => lua,
}
}
} }
impl<'a> Debug<'a> { impl<'a> Debug<'a> {
// We assume the lock is held when this function is called. pub(crate) fn new(lua: &'a RawLua, level: c_int, ar: *mut lua_Debug) -> Self {
#[cfg(not(feature = "luau"))]
pub(crate) fn new(lua: &'a RawLua, ar: *mut lua_Debug) -> Self {
Debug { Debug {
lua: EitherLua::Borrowed(lua), state: lua.state(),
ar: ActivationRecord::Borrowed(ar), lua,
} ar,
} level,
pub(crate) fn new_owned(guard: ReentrantMutexGuard<'a, RawLua>, _level: c_int, ar: lua_Debug) -> Self {
Debug {
lua: EitherLua::Owned(guard),
ar: ActivationRecord::Owned(UnsafeCell::new(ar)),
#[cfg(feature = "luau")]
level: _level,
} }
} }
@@ -74,7 +47,7 @@ impl<'a> Debug<'a> {
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn event(&self) -> DebugEvent { pub fn event(&self) -> DebugEvent {
unsafe { unsafe {
match (*self.ar.get()).event { match (*self.ar).event {
ffi::LUA_HOOKCALL => DebugEvent::Call, ffi::LUA_HOOKCALL => DebugEvent::Call,
ffi::LUA_HOOKRET => DebugEvent::Ret, ffi::LUA_HOOKRET => DebugEvent::Ret,
ffi::LUA_HOOKTAILCALL => DebugEvent::TailCall, ffi::LUA_HOOKTAILCALL => DebugEvent::TailCall,
@@ -85,24 +58,48 @@ impl<'a> Debug<'a> {
} }
} }
/// Corresponds to the `n` what mask. /// Returns the function that is running at the given level.
pub fn names(&self) -> DebugNames { ///
/// Corresponds to the `f` "what" mask.
pub fn function(&self) -> Function {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 1);
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("f"), self.ar) != 0,
"lua_getinfo failed with `f`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.state, self.level, cstr!("f"), self.ar) != 0,
"lua_getinfo failed with `f`"
);
ffi::lua_xmove(self.state, self.lua.ref_thread(), 1);
Function(self.lua.pop_ref_thread())
}
}
/// Corresponds to the `n` "what" mask.
pub fn names(&self) -> DebugNames<'_> {
unsafe { unsafe {
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state(), cstr!("n"), self.ar.get()) != 0, ffi::lua_getinfo(self.state, cstr!("n"), self.ar) != 0,
"lua_getinfo failed with `n`" "lua_getinfo failed with `n`"
); );
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("n"), self.ar.get()) != 0, ffi::lua_getinfo(self.state, self.level, cstr!("n"), self.ar) != 0,
"lua_getinfo failed with `n`" "lua_getinfo failed with `n`"
); );
DebugNames { DebugNames {
name: ptr_to_lossy_str((*self.ar.get()).name), name: ptr_to_lossy_str((*self.ar).name),
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
name_what: match ptr_to_str((*self.ar.get()).namewhat) { name_what: match ptr_to_str((*self.ar).namewhat) {
Some("") => None, Some("") => None,
val => val, val => val,
}, },
@@ -112,119 +109,107 @@ impl<'a> Debug<'a> {
} }
} }
/// Corresponds to the `S` what mask. /// Corresponds to the `S` "what" mask.
pub fn source(&self) -> DebugSource { pub fn source(&self) -> DebugSource<'_> {
unsafe { unsafe {
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state(), cstr!("S"), self.ar.get()) != 0, ffi::lua_getinfo(self.state, cstr!("S"), self.ar) != 0,
"lua_getinfo failed with `S`" "lua_getinfo failed with `S`"
); );
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("s"), self.ar.get()) != 0, ffi::lua_getinfo(self.state, self.level, cstr!("s"), self.ar) != 0,
"lua_getinfo failed with `s`" "lua_getinfo failed with `s`"
); );
DebugSource { DebugSource {
source: ptr_to_lossy_str((*self.ar.get()).source), source: ptr_to_lossy_str((*self.ar).source),
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
short_src: ptr_to_lossy_str((*self.ar.get()).short_src.as_ptr()), short_src: ptr_to_lossy_str((*self.ar).short_src.as_ptr()),
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
short_src: ptr_to_lossy_str((*self.ar.get()).short_src), short_src: ptr_to_lossy_str((*self.ar).short_src),
line_defined: linenumber_to_usize((*self.ar.get()).linedefined), line_defined: linenumber_to_usize((*self.ar).linedefined),
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
last_line_defined: linenumber_to_usize((*self.ar.get()).lastlinedefined), last_line_defined: linenumber_to_usize((*self.ar).lastlinedefined),
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
last_line_defined: None, last_line_defined: None,
what: ptr_to_str((*self.ar.get()).what).unwrap_or("main"), what: ptr_to_str((*self.ar).what).unwrap_or("main"),
} }
} }
} }
/// Corresponds to the `l` what mask. Returns the current line. /// Corresponds to the `l` "what" mask. Returns the current line.
pub fn curr_line(&self) -> i32 { pub fn current_line(&self) -> Option<usize> {
unsafe { unsafe {
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state(), cstr!("l"), self.ar.get()) != 0, ffi::lua_getinfo(self.state, cstr!("l"), self.ar) != 0,
"lua_getinfo failed with `l`" "lua_getinfo failed with `l`"
); );
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("l"), self.ar.get()) != 0, ffi::lua_getinfo(self.state, self.level, cstr!("l"), self.ar) != 0,
"lua_getinfo failed with `l`" "lua_getinfo failed with `l`"
); );
(*self.ar.get()).currentline linenumber_to_usize((*self.ar).currentline)
} }
} }
/// Corresponds to the `t` what mask. Returns true if the hook is in a function tail call, false /// Corresponds to the `t` "what" mask. Returns true if the hook is in a function tail call,
/// otherwise. /// false otherwise.
#[cfg(not(feature = "luau"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] #[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52")))
)]
pub fn is_tail_call(&self) -> bool { pub fn is_tail_call(&self) -> bool {
unsafe { unsafe {
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state(), cstr!("t"), self.ar.get()) != 0, ffi::lua_getinfo(self.state, cstr!("t"), self.ar) != 0,
"lua_getinfo failed with `t`" "lua_getinfo failed with `t`"
); );
(*self.ar.get()).currentline != 0 (*self.ar).istailcall != 0
} }
} }
/// Corresponds to the `u` what mask. /// Corresponds to the `u` "what" mask.
pub fn stack(&self) -> DebugStack { pub fn stack(&self) -> DebugStack {
unsafe { unsafe {
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state(), cstr!("u"), self.ar.get()) != 0, ffi::lua_getinfo(self.state, cstr!("u"), self.ar) != 0,
"lua_getinfo failed with `u`" "lua_getinfo failed with `u`"
); );
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("au"), self.ar.get()) != 0, ffi::lua_getinfo(self.state, self.level, cstr!("au"), self.ar) != 0,
"lua_getinfo failed with `au`" "lua_getinfo failed with `au`"
); );
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
let stack = DebugStack { let stack = DebugStack {
num_ups: (*self.ar.get()).nups as _, num_upvalues: (*self.ar).nups as _,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] #[cfg(not(any(feature = "lua51", feature = "luajit")))]
num_params: (*self.ar.get()).nparams as _, num_params: (*self.ar).nparams as _,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] #[cfg(not(any(feature = "lua51", feature = "luajit")))]
is_vararg: (*self.ar.get()).isvararg != 0, is_vararg: (*self.ar).isvararg != 0,
}; };
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
let stack = DebugStack { let stack = DebugStack {
num_ups: (*self.ar.get()).nupvals, num_upvalues: (*self.ar).nupvals,
num_params: (*self.ar.get()).nparams, num_params: (*self.ar).nparams,
is_vararg: (*self.ar.get()).isvararg != 0, is_vararg: (*self.ar).isvararg != 0,
}; };
stack stack
} }
} }
} }
enum ActivationRecord {
#[cfg(not(feature = "luau"))]
Borrowed(*mut lua_Debug),
Owned(UnsafeCell<lua_Debug>),
}
impl ActivationRecord {
#[inline]
fn get(&self) -> *mut lua_Debug {
match self {
#[cfg(not(feature = "luau"))]
ActivationRecord::Borrowed(x) => *x,
ActivationRecord::Owned(x) => x.get(),
}
}
}
/// Represents a specific event that triggered the hook. /// Represents a specific event that triggered the hook.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DebugEvent { pub enum DebugEvent {
Call, Call,
@@ -235,6 +220,9 @@ pub enum DebugEvent {
Unknown(c_int), Unknown(c_int),
} }
/// Contains the name information of a function in the call stack.
///
/// Returned by the [`Debug::names`] method.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DebugNames<'a> { pub struct DebugNames<'a> {
/// A (reasonable) name of the function (`None` if the name cannot be found). /// A (reasonable) name of the function (`None` if the name cannot be found).
@@ -245,6 +233,9 @@ pub struct DebugNames<'a> {
pub name_what: Option<&'static str>, pub name_what: Option<&'static str>,
} }
/// Contains the source information of a function in the call stack.
///
/// Returned by the [`Debug::source`] method.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DebugSource<'a> { pub struct DebugSource<'a> {
/// Source of the chunk that created the function. /// Source of the chunk that created the function.
@@ -260,23 +251,20 @@ pub struct DebugSource<'a> {
pub what: &'static str, pub what: &'static str,
} }
/// Contains stack information about a function in the call stack.
///
/// Returned by the [`Debug::stack`] method.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct DebugStack { pub struct DebugStack {
/// Number of upvalues. /// The number of upvalues of the function.
pub num_ups: u8, pub num_upvalues: u8,
/// Number of parameters. /// The number of parameters of the function (always 0 for C).
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] #[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))]
#[cfg_attr( #[cfg_attr(docsrs, doc(cfg(not(any(feature = "lua51", feature = "luajit")))))]
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
)]
pub num_params: u8, pub num_params: u8,
/// Whether the function is a vararg function. /// Whether the function is a variadic function (always true for C).
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] #[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))]
#[cfg_attr( #[cfg_attr(docsrs, doc(cfg(not(any(feature = "lua51", feature = "luajit")))))]
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
)]
pub is_vararg: bool, pub is_vararg: bool,
} }
@@ -354,6 +342,7 @@ impl HookTriggers {
} }
// Compute the mask to pass to `lua_sethook`. // Compute the mask to pass to `lua_sethook`.
#[cfg(not(feature = "luau"))]
pub(crate) const fn mask(&self) -> c_int { pub(crate) const fn mask(&self) -> c_int {
let mut mask: c_int = 0; let mut mask: c_int = 0;
if self.on_calls { if self.on_calls {
@@ -373,6 +362,7 @@ impl HookTriggers {
// Returns the `count` parameter to pass to `lua_sethook`, if applicable. Otherwise, zero is // Returns the `count` parameter to pass to `lua_sethook`, if applicable. Otherwise, zero is
// returned. // returned.
#[cfg(not(feature = "luau"))]
pub(crate) const fn count(&self) -> c_int { pub(crate) const fn count(&self) -> c_int {
match self.every_nth_instruction { match self.every_nth_instruction {
Some(n) => n as c_int, Some(n) => n as c_int,
@@ -382,7 +372,7 @@ impl HookTriggers {
} }
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
impl BitOr for HookTriggers { impl std::ops::BitOr for HookTriggers {
type Output = Self; type Output = Self;
fn bitor(mut self, rhs: Self) -> Self::Output { fn bitor(mut self, rhs: Self) -> Self::Output {
@@ -397,7 +387,7 @@ impl BitOr for HookTriggers {
} }
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
impl BitOrAssign for HookTriggers { impl std::ops::BitOrAssign for HookTriggers {
fn bitor_assign(&mut self, rhs: Self) { fn bitor_assign(&mut self, rhs: Self) {
*self = *self | rhs; *self = *self | rhs;
} }
+38 -49
View File
@@ -4,7 +4,6 @@ use std::io::Error as IoError;
use std::net::AddrParseError; use std::net::AddrParseError;
use std::result::Result as StdResult; use std::result::Result as StdResult;
use std::str::Utf8Error; use std::str::Utf8Error;
use std::string::String as StdString;
use std::sync::Arc; use std::sync::Arc;
use crate::private::Sealed; use crate::private::Sealed;
@@ -22,7 +21,7 @@ pub enum Error {
/// Syntax error while parsing Lua source code. /// Syntax error while parsing Lua source code.
SyntaxError { SyntaxError {
/// The error message as returned by Lua. /// The error message as returned by Lua.
message: StdString, message: String,
/// `true` if the error can likely be fixed by appending more input to the source code. /// `true` if the error can likely be fixed by appending more input to the source code.
/// ///
/// This is useful for implementing REPLs as they can query the user for more input if this /// This is useful for implementing REPLs as they can query the user for more input if this
@@ -34,20 +33,20 @@ pub enum Error {
/// The Lua VM returns this error when a builtin operation is performed on incompatible types. /// The Lua VM returns this error when a builtin operation is performed on incompatible types.
/// Among other things, this includes invoking operators on wrong types (such as calling or /// Among other things, this includes invoking operators on wrong types (such as calling or
/// indexing a `nil` value). /// indexing a `nil` value).
RuntimeError(StdString), RuntimeError(String),
/// Lua memory error, aka `LUA_ERRMEM` /// Lua memory error, aka `LUA_ERRMEM`
/// ///
/// The Lua VM returns this error when the allocator does not return the requested memory, aka /// The Lua VM returns this error when the allocator does not return the requested memory, aka
/// it is an out-of-memory error. /// it is an out-of-memory error.
MemoryError(StdString), MemoryError(String),
/// Lua garbage collector error, aka `LUA_ERRGCMM`. /// Lua garbage collector error, aka `LUA_ERRGCMM`.
/// ///
/// The Lua VM returns this error when there is an error running a `__gc` metamethod. /// The Lua VM returns this error when there is an error running a `__gc` metamethod.
#[cfg(any(feature = "lua53", feature = "lua52", doc))] #[cfg(any(feature = "lua53", feature = "lua52", doc))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua53", feature = "lua52"))))] #[cfg_attr(docsrs, doc(cfg(any(feature = "lua53", feature = "lua52"))))]
GarbageCollectorError(StdString), GarbageCollectorError(String),
/// Potentially unsafe action in safe mode. /// Potentially unsafe action in safe mode.
SafetyError(StdString), SafetyError(String),
/// Memory control is not available. /// Memory control is not available.
/// ///
/// This error can only happen when Lua state was not created by us and does not have the /// This error can only happen when Lua state was not created by us and does not have the
@@ -80,23 +79,14 @@ pub enum Error {
/// (which is stored in the corresponding field). /// (which is stored in the corresponding field).
BadArgument { BadArgument {
/// Function that was called. /// Function that was called.
to: Option<StdString>, to: Option<String>,
/// Argument position (usually starts from 1). /// Argument position (usually starts from 1).
pos: usize, pos: usize,
/// Argument name. /// Argument name.
name: Option<StdString>, name: Option<String>,
/// Underlying error returned when converting argument to a Lua value. /// Underlying error returned when converting argument to a Lua value.
cause: Arc<Error>, cause: Arc<Error>,
}, },
/// A Rust value could not be converted to a Lua value.
ToLuaConversionError {
/// Name of the Rust type that could not be converted.
from: String,
/// Name of the Lua type that could not be created.
to: &'static str,
/// A message indicating why the conversion failed in more detail.
message: Option<StdString>,
},
/// A Lua value could not be converted to the expected Rust type. /// A Lua value could not be converted to the expected Rust type.
FromLuaConversionError { FromLuaConversionError {
/// Name of the Lua type that could not be converted. /// Name of the Lua type that could not be converted.
@@ -104,7 +94,7 @@ pub enum Error {
/// Name of the Rust type that could not be created. /// Name of the Rust type that could not be created.
to: String, to: String,
/// A string containing more detailed error information. /// A string containing more detailed error information.
message: Option<StdString>, message: Option<String>,
}, },
/// [`Thread::resume`] was called on an unresumable coroutine. /// [`Thread::resume`] was called on an unresumable coroutine.
/// ///
@@ -154,17 +144,17 @@ pub enum Error {
/// A [`MetaMethod`] operation is restricted (typically for `__gc` or `__metatable`). /// A [`MetaMethod`] operation is restricted (typically for `__gc` or `__metatable`).
/// ///
/// [`MetaMethod`]: crate::MetaMethod /// [`MetaMethod`]: crate::MetaMethod
MetaMethodRestricted(StdString), MetaMethodRestricted(String),
/// A [`MetaMethod`] (eg. `__index` or `__newindex`) has invalid type. /// A [`MetaMethod`] (eg. `__index` or `__newindex`) has invalid type.
/// ///
/// [`MetaMethod`]: crate::MetaMethod /// [`MetaMethod`]: crate::MetaMethod
MetaMethodTypeError { MetaMethodTypeError {
/// Name of the metamethod. /// Name of the metamethod.
method: StdString, method: String,
/// Passed value type. /// Passed value type.
type_name: &'static str, type_name: &'static str,
/// A string containing more detailed error information. /// A string containing more detailed error information.
message: Option<StdString>, message: Option<String>,
}, },
/// A [`RegistryKey`] produced from a different Lua state was used. /// A [`RegistryKey`] produced from a different Lua state was used.
/// ///
@@ -173,7 +163,7 @@ pub enum Error {
/// A Rust callback returned `Err`, raising the contained `Error` as a Lua error. /// A Rust callback returned `Err`, raising the contained `Error` as a Lua error.
CallbackError { CallbackError {
/// Lua call stack backtrace. /// Lua call stack backtrace.
traceback: StdString, traceback: String,
/// Original error returned by the Rust code. /// Original error returned by the Rust code.
cause: Arc<Error>, cause: Arc<Error>,
}, },
@@ -183,13 +173,13 @@ pub enum Error {
/// and returned again. /// and returned again.
PreviouslyResumedPanic, PreviouslyResumedPanic,
/// Serialization error. /// Serialization error.
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
SerializeError(StdString), SerializeError(String),
/// Deserialization error. /// Deserialization error.
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
DeserializeError(StdString), DeserializeError(String),
/// A custom error. /// A custom error.
/// ///
/// This can be used for returning user-defined errors from callbacks. /// This can be used for returning user-defined errors from callbacks.
@@ -201,7 +191,7 @@ pub enum Error {
/// An error with additional context. /// An error with additional context.
WithContext { WithContext {
/// A string containing additional context. /// A string containing additional context.
context: StdString, context: String,
/// Underlying error. /// Underlying error.
cause: Arc<Error>, cause: Arc<Error>,
}, },
@@ -225,7 +215,7 @@ impl fmt::Display for Error {
} }
Error::SafetyError(msg) => { Error::SafetyError(msg) => {
write!(fmt, "safety error: {msg}") write!(fmt, "safety error: {msg}")
}, }
Error::MemoryControlNotAvailable => { Error::MemoryControlNotAvailable => {
write!(fmt, "memory control is not available") write!(fmt, "memory control is not available")
} }
@@ -238,10 +228,7 @@ impl fmt::Display for Error {
fmt, fmt,
"out of Lua stack, too many arguments to a Lua function or too many return values from a callback" "out of Lua stack, too many arguments to a Lua function or too many return values from a callback"
), ),
Error::BindError => write!( Error::BindError => write!(fmt, "too many arguments to Function::bind"),
fmt,
"too many arguments to Function::bind"
),
Error::BadArgument { to, pos, name, cause } => { Error::BadArgument { to, pos, name, cause } => {
if let Some(name) = name { if let Some(name) = name {
write!(fmt, "bad argument `{name}`")?; write!(fmt, "bad argument `{name}`")?;
@@ -252,13 +239,6 @@ impl fmt::Display for Error {
write!(fmt, " to `{to}`")?; write!(fmt, " to `{to}`")?;
} }
write!(fmt, ": {cause}") write!(fmt, ": {cause}")
},
Error::ToLuaConversionError { from, to, message } => {
write!(fmt, "error converting {from} to Lua {to}")?;
match message {
None => Ok(()),
Some(message) => write!(fmt, " ({message})"),
}
} }
Error::FromLuaConversionError { from, to, message } => { Error::FromLuaConversionError { from, to, message } => {
write!(fmt, "error converting Lua {from} to {to}")?; write!(fmt, "error converting Lua {from} to {to}")?;
@@ -273,7 +253,11 @@ impl fmt::Display for Error {
Error::UserDataBorrowError => write!(fmt, "error borrowing userdata"), Error::UserDataBorrowError => write!(fmt, "error borrowing userdata"),
Error::UserDataBorrowMutError => write!(fmt, "error mutably borrowing userdata"), Error::UserDataBorrowMutError => write!(fmt, "error mutably borrowing userdata"),
Error::MetaMethodRestricted(method) => write!(fmt, "metamethod {method} is restricted"), Error::MetaMethodRestricted(method) => write!(fmt, "metamethod {method} is restricted"),
Error::MetaMethodTypeError { method, type_name, message } => { Error::MetaMethodTypeError {
method,
type_name,
message,
} => {
write!(fmt, "metamethod {method} has unsupported type {type_name}")?; write!(fmt, "metamethod {method} has unsupported type {type_name}")?;
match message { match message {
None => Ok(()), None => Ok(()),
@@ -286,7 +270,11 @@ impl fmt::Display for Error {
Error::CallbackError { cause, traceback } => { Error::CallbackError { cause, traceback } => {
// Trace errors down to the root // Trace errors down to the root
let (mut cause, mut full_traceback) = (cause, None); let (mut cause, mut full_traceback) = (cause, None);
while let Error::CallbackError { cause: cause2, traceback: traceback2 } = &**cause { while let Error::CallbackError {
cause: cause2,
traceback: traceback2,
} = &**cause
{
cause = cause2; cause = cause2;
full_traceback = Some(traceback2); full_traceback = Some(traceback2);
} }
@@ -309,14 +297,14 @@ impl fmt::Display for Error {
Error::PreviouslyResumedPanic => { Error::PreviouslyResumedPanic => {
write!(fmt, "previously resumed panic returned again") write!(fmt, "previously resumed panic returned again")
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
Error::SerializeError(err) => { Error::SerializeError(err) => {
write!(fmt, "serialize error: {err}") write!(fmt, "serialize error: {err}")
}, }
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
Error::DeserializeError(err) => { Error::DeserializeError(err) => {
write!(fmt, "deserialize error: {err}") write!(fmt, "deserialize error: {err}")
}, }
Error::ExternalError(err) => err.fmt(fmt), Error::ExternalError(err) => err.fmt(fmt),
Error::WithContext { context, cause } => { Error::WithContext { context, cause } => {
writeln!(fmt, "{context}")?; writeln!(fmt, "{context}")?;
@@ -394,6 +382,7 @@ impl Error {
} }
} }
#[inline]
pub(crate) fn from_lua_conversion( pub(crate) fn from_lua_conversion(
from: &'static str, from: &'static str,
to: impl ToString, to: impl ToString,
@@ -494,14 +483,14 @@ impl From<Utf8Error> for Error {
} }
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
impl serde::ser::Error for Error { impl serde::ser::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self { fn custom<T: fmt::Display>(msg: T) -> Self {
Self::SerializeError(msg.to_string()) Self::SerializeError(msg.to_string())
} }
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
impl serde::de::Error for Error { impl serde::de::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self { fn custom<T: fmt::Display>(msg: T) -> Self {
Self::DeserializeError(msg.to_string()) Self::DeserializeError(msg.to_string())
+160 -34
View File
@@ -1,3 +1,84 @@
//! Lua function handling.
//!
//! This module provides types for working with Lua functions from Rust, including
//! both Lua-defined functions and native Rust callbacks.
//!
//! # Main Types
//!
//! - [`Function`] - A handle to a Lua function that can be called from Rust.
//! - [`FunctionInfo`] - Debug information about a function (name, source, line numbers, etc.).
//! - [`CoverageInfo`] - Code coverage data for Luau functions (requires `luau` feature).
//!
//! # Calling Functions
//!
//! Use [`Function::call`] to invoke a Lua function synchronously:
//!
//! ```
//! # use mlua::{Function, Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//!
//! // Get a built-in function
//! let print: Function = lua.globals().get("print")?;
//! print.call::<()>("Hello from Rust!")?;
//!
//! // Call a function that returns values
//! let tonumber: Function = lua.globals().get("tonumber")?;
//! let n: i32 = tonumber.call("42")?;
//! assert_eq!(n, 42);
//! # Ok(())
//! # }
//! ```
//!
//! For asynchronous execution, use `Function::call_async` (requires `async` feature):
//!
//! ```ignore
//! let result: String = my_async_func.call_async(args).await?;
//! ```
//!
//! # Creating Functions
//!
//! Functions can be created from Rust closures using [`Lua::create_function`]:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//!
//! let greet = lua.create_function(|_, name: String| {
//! Ok(format!("Hello, {}!", name))
//! })?;
//!
//! lua.globals().set("greet", greet)?;
//! let result: String = lua.load(r#"greet("World")"#).eval()?;
//! assert_eq!(result, "Hello, World!");
//! # Ok(())
//! # }
//! ```
//!
//! For simpler cases, use [`Function::wrap`] or [`Function::wrap_raw`] to convert a Rust function
//! directly:
//!
//! ```
//! # use mlua::{Function, Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//!
//! fn add(a: i32, b: i32) -> i32 { a + b }
//!
//! lua.globals().set("add", Function::wrap_raw(add))?;
//! let sum: i32 = lua.load("add(2, 3)").eval()?;
//! assert_eq!(sum, 5);
//! # Ok(())
//! # }
//! ```
//!
//! # Function Environments
//!
//! Lua functions have an associated environment table that determines how global
//! variables are resolved. Use [`Function::environment`] and [`Function::set_environment`]
//! to inspect or modify this environment.
use std::cell::RefCell; use std::cell::RefCell;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use std::{mem, ptr, slice}; use std::{mem, ptr, slice};
@@ -8,7 +89,7 @@ use crate::table::Table;
use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut}; use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut};
use crate::types::{Callback, LuaType, MaybeSend, ValueRef}; use crate::types::{Callback, LuaType, MaybeSend, ValueRef};
use crate::util::{ use crate::util::{
assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str, StackGuard, StackGuard, assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str,
}; };
use crate::value::Value; use crate::value::Value;
@@ -18,7 +99,7 @@ use {
crate::traits::LuaNativeAsyncFn, crate::traits::LuaNativeAsyncFn,
crate::types::AsyncCallback, crate::types::AsyncCallback,
std::future::{self, Future}, std::future::{self, Future},
std::pin::Pin, std::pin::{Pin, pin},
std::task::{Context, Poll}, std::task::{Context, Poll},
}; };
@@ -32,6 +113,7 @@ pub struct Function(pub(crate) ValueRef);
/// ///
/// [`Lua Debug Interface`]: https://www.lua.org/manual/5.4/manual.html#4.7 /// [`Lua Debug Interface`]: https://www.lua.org/manual/5.4/manual.html#4.7
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[non_exhaustive]
pub struct FunctionInfo { pub struct FunctionInfo {
/// A (reasonable) name of the function (`None` if the name cannot be found). /// A (reasonable) name of the function (`None` if the name cannot be found).
pub name: Option<String>, pub name: Option<String>,
@@ -50,6 +132,16 @@ pub struct FunctionInfo {
pub line_defined: Option<usize>, pub line_defined: Option<usize>,
/// The line number where the definition of the function ends (not set by Luau). /// The line number where the definition of the function ends (not set by Luau).
pub last_line_defined: Option<usize>, pub last_line_defined: Option<usize>,
/// The number of upvalues of the function.
pub num_upvalues: u8,
/// The number of parameters of the function (always 0 for C).
#[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))]
#[cfg_attr(docsrs, doc(cfg(not(any(feature = "lua51", feature = "luajit")))))]
pub num_params: u8,
/// Whether the function is a variadic function (always true for C).
#[cfg(any(not(any(feature = "lua51", feature = "luajit")), doc))]
#[cfg_attr(docsrs, doc(cfg(not(any(feature = "lua51", feature = "luajit")))))]
pub is_vararg: bool,
} }
/// Luau function coverage snapshot. /// Luau function coverage snapshot.
@@ -253,7 +345,7 @@ impl Function {
"#, "#,
) )
.try_cache() .try_cache()
.set_name("__mlua_bind") .set_name("=__mlua_bind")
.call((self, args_wrapper)) .call((self, args_wrapper))
} }
@@ -276,7 +368,7 @@ impl Function {
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::lua_getfenv(state, -1); ffi::lua_getfenv(state, -1);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
for i in 1..=255 { for i in 1..=255 {
// Traverse upvalues until we find the _ENV one // Traverse upvalues until we find the _ENV one
match ffi::lua_getupvalue(state, -1, i) { match ffi::lua_getupvalue(state, -1, i) {
@@ -316,7 +408,7 @@ impl Function {
lua.push_ref(&env.0); lua.push_ref(&env.0);
ffi::lua_setfenv(state, -2); ffi::lua_setfenv(state, -2);
} }
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
for i in 1..=255 { for i in 1..=255 {
match ffi::lua_getupvalue(state, -1, i) { match ffi::lua_getupvalue(state, -1, i) {
s if s.is_null() => return Ok(false), s if s.is_null() => return Ok(false),
@@ -343,7 +435,8 @@ impl Function {
/// Returns information about the function. /// Returns information about the function.
/// ///
/// Corresponds to the `>Sn` what mask for [`lua_getinfo`] when applied to the function. /// Corresponds to the `>Snu` (`>Sn` for Luau) what mask for
/// [`lua_getinfo`] when applied to the function.
/// ///
/// [`lua_getinfo`]: https://www.lua.org/manual/5.4/manual.html#lua_getinfo /// [`lua_getinfo`]: https://www.lua.org/manual/5.4/manual.html#lua_getinfo
pub fn info(&self) -> FunctionInfo { pub fn info(&self) -> FunctionInfo {
@@ -355,11 +448,16 @@ impl Function {
let mut ar: ffi::lua_Debug = mem::zeroed(); let mut ar: ffi::lua_Debug = mem::zeroed();
lua.push_ref(&self.0); lua.push_ref(&self.0);
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
let res = ffi::lua_getinfo(state, cstr!(">Sn"), &mut ar); let res = ffi::lua_getinfo(state, cstr!(">Snu"), &mut ar);
#[cfg(not(feature = "luau"))]
mlua_assert!(res != 0, "lua_getinfo failed with `>Snu`");
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
let res = ffi::lua_getinfo(state, -1, cstr!("sn"), &mut ar); let res = ffi::lua_getinfo(state, -1, cstr!("snau"), &mut ar);
mlua_assert!(res != 0, "lua_getinfo failed with `>Sn`"); #[cfg(feature = "luau")]
mlua_assert!(res != 0, "lua_getinfo failed with `snau`");
FunctionInfo { FunctionInfo {
name: ptr_to_lossy_str(ar.name).map(|s| s.into_owned()), name: ptr_to_lossy_str(ar.name).map(|s| s.into_owned()),
@@ -381,6 +479,14 @@ impl Function {
last_line_defined: linenumber_to_usize(ar.lastlinedefined), last_line_defined: linenumber_to_usize(ar.lastlinedefined),
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
last_line_defined: None, last_line_defined: None,
#[cfg(not(feature = "luau"))]
num_upvalues: ar.nups as _,
#[cfg(feature = "luau")]
num_upvalues: ar.nupvals,
#[cfg(not(any(feature = "lua51", feature = "luajit")))]
num_params: ar.nparams,
#[cfg(not(any(feature = "lua51", feature = "luajit")))]
is_vararg: ar.isvararg != 0,
} }
} }
} }
@@ -400,11 +506,14 @@ impl Function {
_state: *mut ffi::lua_State, _state: *mut ffi::lua_State,
buf: *const c_void, buf: *const c_void,
buf_len: usize, buf_len: usize,
data: *mut c_void, data_ptr: *mut c_void,
) -> c_int { ) -> c_int {
let data = &mut *(data as *mut Vec<u8>); // If `data` is null, then it's a signal that write is finished.
let buf = slice::from_raw_parts(buf as *const u8, buf_len); if !data_ptr.is_null() && buf_len > 0 {
data.extend_from_slice(buf); let data = &mut *(data_ptr as *mut Vec<u8>);
let buf = slice::from_raw_parts(buf as *const u8, buf_len);
data.extend_from_slice(buf);
}
0 0
} }
@@ -434,7 +543,7 @@ impl Function {
/// [`Compiler::set_coverage_level`]: crate::chunk::Compiler::set_coverage_level /// [`Compiler::set_coverage_level`]: crate::chunk::Compiler::set_coverage_level
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn coverage<F>(&self, mut func: F) pub fn coverage<F>(&self, func: F)
where where
F: FnMut(CoverageInfo), F: FnMut(CoverageInfo),
{ {
@@ -454,13 +563,16 @@ impl Function {
} else { } else {
None None
}; };
let rust_callback = &mut *(data as *mut F); let rust_callback = &*(data as *const RefCell<F>);
rust_callback(CoverageInfo { if let Ok(mut rust_callback) = rust_callback.try_borrow_mut() {
function, // Call the Rust callback with CoverageInfo
line_defined, rust_callback(CoverageInfo {
depth, function,
hits: slice::from_raw_parts(hits, size).to_vec(), line_defined,
}); depth,
hits: slice::from_raw_parts(hits, size).to_vec(),
});
}
} }
let lua = self.0.lua.lock(); let lua = self.0.lua.lock();
@@ -470,7 +582,8 @@ impl Function {
assert_stack(state, 1); assert_stack(state, 1);
lua.push_ref(&self.0); lua.push_ref(&self.0);
let func_ptr = &mut func as *mut F as *mut c_void; let func = RefCell::new(func);
let func_ptr = &func as *const RefCell<F> as *mut c_void;
ffi::lua_getcoverage(state, -1, func_ptr, callback::<F>); ffi::lua_getcoverage(state, -1, func_ptr, callback::<F>);
} }
} }
@@ -492,16 +605,24 @@ impl Function {
/// This function returns shallow clone (same handle) for Rust/C functions. /// This function returns shallow clone (same handle) for Rust/C functions.
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn deep_clone(&self) -> Self { pub fn deep_clone(&self) -> Result<Self> {
let lua = self.0.lua.lock(); let lua = self.0.lua.lock();
let ref_thread = lua.ref_thread(); let state = lua.state();
unsafe { unsafe {
if ffi::lua_iscfunction(ref_thread, self.0.index) != 0 { let _sg = StackGuard::new(state);
return self.clone(); check_stack(state, 2)?;
lua.push_ref(&self.0);
if ffi::lua_iscfunction(state, -1) != 0 {
return Ok(self.clone());
} }
ffi::lua_clonefunction(ref_thread, self.0.index); if lua.unlikely_memory_error() {
Function(lua.pop_ref_thread()) ffi::lua_clonefunction(state, -1);
} else {
protect_lua!(state, 1, 1, fn(state) ffi::lua_clonefunction(state, -1))?;
}
Ok(Function(lua.pop_ref()))
} }
} }
} }
@@ -641,22 +762,27 @@ impl LuaType for Function {
const TYPE_ID: c_int = ffi::LUA_TFUNCTION; const TYPE_ID: c_int = ffi::LUA_TFUNCTION;
} }
/// Future for asynchronous function calls.
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[must_use = "futures do nothing unless you `.await` or poll them"] #[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncCallFuture<R: FromLuaMulti>(Result<AsyncThread<R>>); pub struct AsyncCallFuture<R: FromLuaMulti>(Result<AsyncThread<R>>);
#[cfg(feature = "async")]
impl<R: FromLuaMulti> AsyncCallFuture<R> {
pub(crate) fn error(err: Error) -> Self {
AsyncCallFuture(Err(err))
}
}
#[cfg(feature = "async")] #[cfg(feature = "async")]
impl<R: FromLuaMulti> Future for AsyncCallFuture<R> { impl<R: FromLuaMulti> Future for AsyncCallFuture<R> {
type Output = Result<R>; type Output = Result<R>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// Safety: We're not moving any pinned data let this = self.get_mut();
let this = unsafe { self.get_unchecked_mut() };
match &mut this.0 { match &mut this.0 {
Ok(thread) => { Ok(thread) => pin!(thread).poll(cx),
let pinned_thread = unsafe { Pin::new_unchecked(thread) };
pinned_thread.poll(cx)
}
Err(err) => Poll::Ready(Err(err.clone())), Err(err) => Poll::Ready(Err(err.clone())),
} }
} }
+18 -18
View File
@@ -35,7 +35,7 @@
//! The [`Value`] enum and other types implement [`serde::Serialize`] trait to support serializing //! The [`Value`] enum and other types implement [`serde::Serialize`] trait to support serializing
//! Lua values into Rust values. //! Lua values into Rust values.
//! //!
//! Requires `feature = "serialize"`. //! Requires `feature = "serde"`.
//! //!
//! # Async/await support //! # Async/await support
//! //!
@@ -66,7 +66,6 @@
// warnings at all. // warnings at all.
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(send), allow(clippy::arc_with_non_send_sync))] #![cfg_attr(not(send), allow(clippy::arc_with_non_send_sync))]
#![allow(clippy::ptr_eq)]
#![allow(unsafe_op_in_unsafe_fn)] #![allow(unsafe_op_in_unsafe_fn)]
#[macro_use] #[macro_use]
@@ -76,8 +75,6 @@ mod buffer;
mod chunk; mod chunk;
mod conversion; mod conversion;
mod error; mod error;
mod function;
mod hook;
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
mod luau; mod luau;
mod memory; mod memory;
@@ -86,7 +83,6 @@ mod scope;
mod state; mod state;
mod stdlib; mod stdlib;
mod string; mod string;
mod table;
mod thread; mod thread;
mod traits; mod traits;
mod types; mod types;
@@ -95,21 +91,23 @@ mod util;
mod value; mod value;
mod vector; mod vector;
pub mod debug;
pub mod function;
pub mod prelude; pub mod prelude;
pub mod table;
pub use bstr::BString; pub use bstr::BString;
pub use ffi::{self, lua_CFunction, lua_State}; pub use ffi::{self, lua_CFunction, lua_State};
pub use crate::chunk::{AsChunk, Chunk, ChunkMode}; pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result}; pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result};
pub use crate::function::{Function, FunctionInfo}; pub use crate::function::Function;
pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack};
pub use crate::multi::{MultiValue, Variadic}; pub use crate::multi::{MultiValue, Variadic};
pub use crate::scope::Scope; pub use crate::scope::Scope;
pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua}; pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua};
pub use crate::stdlib::StdLib; pub use crate::stdlib::StdLib;
pub use crate::string::{BorrowedBytes, BorrowedStr, String}; pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString, LuaString as String};
pub use crate::table::{Table, TablePairs, TableSequence}; pub use crate::table::Table;
pub use crate::thread::{Thread, ThreadStatus}; pub use crate::thread::{Thread, ThreadStatus};
pub use crate::traits::{ pub use crate::traits::{
FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut, ObjectLike, FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut, ObjectLike,
@@ -124,15 +122,14 @@ pub use crate::userdata::{
pub use crate::value::{Nil, Value}; pub use crate::value::{Nil, Value};
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
pub use crate::hook::HookTriggers; pub use crate::debug::HookTriggers;
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub use crate::{ pub use crate::{
buffer::Buffer, buffer::Buffer,
chunk::{CompileConstant, Compiler}, chunk::{CompileConstant, Compiler},
function::CoverageInfo, luau::{HeapDump, NavigateError, Require, TextRequirer},
luau::{NavigateError, Require},
vector::Vector, vector::Vector,
}; };
@@ -140,12 +137,15 @@ pub use crate::{
#[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn}; pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn};
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
#[doc(inline)] #[doc(inline)]
pub use crate::serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt}; pub use crate::{
serde::{LuaSerdeExt, de::Options as DeserializeOptions, ser::Options as SerializeOptions},
value::SerializableValue,
};
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub mod serde; pub mod serde;
#[cfg(feature = "mlua_derive")] #[cfg(feature = "mlua_derive")]
@@ -217,7 +217,7 @@ pub use mlua_derive::FromLua;
/// ///
/// You can register multiple entrypoints as required. /// You can register multiple entrypoints as required.
/// ///
/// ``` /// ```ignore
/// use mlua::{Lua, Result, Table}; /// use mlua::{Lua, Result, Table};
/// ///
/// #[mlua::lua_module] /// #[mlua::lua_module]
@@ -254,7 +254,7 @@ pub use mlua_derive::FromLua;
/// ... /// ...
/// } /// }
/// ``` /// ```
#[cfg(any(feature = "module", docsrs))] #[cfg(all(feature = "mlua_derive", any(feature = "module", doc)))]
#[cfg_attr(docsrs, doc(cfg(feature = "module")))] #[cfg_attr(docsrs, doc(cfg(feature = "module")))]
pub use mlua_derive::lua_module; pub use mlua_derive::lua_module;
+178
View File
@@ -0,0 +1,178 @@
use std::collections::HashMap;
use std::hash::Hash;
use std::mem;
use std::os::raw::c_char;
use crate::state::ExtraData;
use super::json::{self, Json};
/// Represents a heap dump of a Luau memory state.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub struct HeapDump {
data: Json<'static>, // refers to the contents of `buf`
buf: Box<str>,
}
impl HeapDump {
/// Dumps the current Lua heap state.
pub(crate) unsafe fn new(state: *mut ffi::lua_State) -> Option<Self> {
unsafe extern "C" fn category_name(state: *mut ffi::lua_State, cat: u8) -> *const c_char {
(&*ExtraData::get(state))
.mem_categories
.get(cat as usize)
.map(|s| s.as_ptr())
.unwrap_or(cstr!("unknown"))
}
let mut buf = Vec::new();
unsafe {
let file = libc::tmpfile();
if file.is_null() {
return None;
}
ffi::lua_gcdump(state, file as *mut _, Some(category_name));
libc::fseek(file, 0, libc::SEEK_END);
let len = libc::ftell(file) as usize;
libc::rewind(file);
if len > 0 {
buf.reserve(len);
libc::fread(buf.as_mut_ptr() as *mut _, 1, len, file);
buf.set_len(len);
}
libc::fclose(file);
}
let buf = String::from_utf8(buf).ok()?.into_boxed_str();
let data = json::parse(unsafe { mem::transmute::<&str, &'static str>(&buf) }).ok()?;
Some(HeapDump { data, buf })
}
/// Returns the raw JSON representation of the heap dump.
///
/// The JSON structure is an internal detail and may change in future versions.
#[doc(hidden)]
pub fn to_json(&self) -> &str {
&self.buf
}
/// Returns the total size of the Lua heap in bytes.
pub fn size(&self) -> u64 {
self.data["stats"]["size"].as_u64().unwrap_or_default()
}
/// Returns a mapping from object type to (count, total size in bytes).
///
/// If `category` is provided, only objects in that category are considered.
pub fn size_by_type<'a>(&'a self, category: Option<&str>) -> HashMap<&'a str, (usize, u64)> {
self.size_by_type_inner(category).unwrap_or_default()
}
fn size_by_type_inner<'a>(&'a self, category: Option<&str>) -> Option<HashMap<&'a str, (usize, u64)>> {
let category_id = match category {
// If we cannot find the category, return empty result
Some(cat) => Some(self.find_category_id(cat)?),
None => None,
};
let mut size_by_type = HashMap::new();
let objects = self.data["objects"].as_object()?;
for obj in objects.values() {
if let Some(cat_id) = category_id
&& obj["cat"].as_i64()? != cat_id
{
continue;
}
update_size(&mut size_by_type, obj["type"].as_str()?, obj["size"].as_u64()?);
}
Some(size_by_type)
}
/// Returns a mapping from category name to total size in bytes.
pub fn size_by_category(&self) -> HashMap<&str, u64> {
let mut size_by_category = HashMap::new();
if let Some(categories) = self.data["stats"]["categories"].as_object() {
for cat in categories.values() {
if let Some(cat_name) = cat["name"].as_str() {
size_by_category.insert(cat_name, cat["size"].as_u64().unwrap_or_default());
}
}
}
size_by_category
}
/// Returns a mapping from userdata type to (count, total size in bytes).
pub fn size_by_userdata<'a>(&'a self, category: Option<&str>) -> HashMap<&'a str, (usize, u64)> {
self.size_by_userdata_inner(category).unwrap_or_default()
}
fn size_by_userdata_inner<'a>(
&'a self,
category: Option<&str>,
) -> Option<HashMap<&'a str, (usize, u64)>> {
let category_id = match category {
// If we cannot find the category, return empty result
Some(cat) => Some(self.find_category_id(cat)?),
None => None,
};
let mut size_by_userdata = HashMap::new();
let objects = self.data["objects"].as_object()?;
for obj in objects.values() {
if obj["type"] != "userdata" {
continue;
}
if let Some(cat_id) = category_id
&& obj["cat"].as_i64()? != cat_id
{
continue;
}
// Determine userdata type from metatable
let mut ud_type = "unknown";
if let Some(metatable_addr) = obj["metatable"].as_str()
&& let Some(t) = get_key(objects, &objects[metatable_addr], "__type")
{
ud_type = t;
}
update_size(&mut size_by_userdata, ud_type, obj["size"].as_u64()?);
}
Some(size_by_userdata)
}
/// Finds the category ID for a given category name.
fn find_category_id(&self, category: &str) -> Option<i64> {
let categories = self.data["stats"]["categories"].as_object()?;
for (cat_id, cat) in categories {
if cat["name"].as_str() == Some(category) {
return cat_id.parse().ok();
}
}
None
}
}
/// Updates the size mapping for a given key.
fn update_size<K: Eq + Hash>(size_type: &mut HashMap<K, (usize, u64)>, key: K, size: u64) {
let (count, total_size) = size_type.entry(key).or_insert((0, 0));
*count += 1;
*total_size += size;
}
/// Retrieves the value associated with a given `key` from a Lua table `tbl`.
fn get_key<'a>(objects: &'a HashMap<&'a str, Json>, tbl: &Json, key: &str) -> Option<&'a str> {
let pairs = tbl["pairs"].as_array()?;
for kv in pairs.chunks_exact(2) {
#[rustfmt::skip]
let (Some(key_addr), Some(val_addr)) = (kv[0].as_str(), kv[1].as_str()) else { continue; };
if objects[key_addr]["type"] == "string" && objects[key_addr]["data"].as_str() == Some(key) {
if objects[val_addr]["type"] == "string" {
return objects[val_addr]["data"].as_str();
} else {
break;
}
}
}
None
}
+327
View File
@@ -0,0 +1,327 @@
use std::array;
use std::collections::HashMap;
use std::iter::Peekable;
use std::ops::Index;
use std::str::CharIndices;
// A simple JSON parser and representation.
// This parser supports only a subset of JSON specification and is intended for Luau's use cases.
#[derive(Debug, PartialEq)]
pub(crate) enum Json<'a> {
Null,
Bool(bool),
Integer(i64),
Number(f64),
String(&'a str),
Array(Vec<Json<'a>>),
Object(HashMap<&'a str, Json<'a>>),
}
impl<'a> Index<&str> for Json<'a> {
type Output = Json<'a>;
fn index(&self, key: &str) -> &Self::Output {
match self {
Json::Object(map) => map.get(key).unwrap_or(&Json::Null),
_ => &Json::Null,
}
}
}
impl PartialEq<&str> for Json<'_> {
fn eq(&self, other: &&str) -> bool {
matches!(self, Json::String(s) if s == other)
}
}
impl<'a> Json<'a> {
pub(crate) fn as_str(&self) -> Option<&'a str> {
match self {
Json::String(s) => Some(s),
_ => None,
}
}
pub(crate) fn as_i64(&self) -> Option<i64> {
match self {
Json::Integer(i) => Some(*i),
Json::Number(n) if n.fract() == 0.0 => Some(*n as i64),
_ => None,
}
}
pub(crate) fn as_u64(&self) -> Option<u64> {
self.as_i64()
.and_then(|i| if i >= 0 { Some(i as u64) } else { None })
}
pub(crate) fn as_array(&self) -> Option<&[Json<'a>]> {
match self {
Json::Array(arr) => Some(arr),
_ => None,
}
}
pub(crate) fn as_object(&self) -> Option<&HashMap<&'a str, Json<'a>>> {
match self {
Json::Object(map) => Some(map),
_ => None,
}
}
}
pub(crate) fn parse<'a>(s: &'a str) -> Result<Json<'a>, &'static str> {
let s = s.trim_ascii();
let mut chars = s.char_indices().peekable();
let value = parse_value(s, &mut chars)?;
Ok(value)
}
fn parse_value<'a>(s: &'a str, chars: &mut Peekable<CharIndices>) -> Result<Json<'a>, &'static str> {
skip_whitespace(chars);
match chars.peek() {
Some((_, '{')) => parse_object(s, chars),
Some((_, '[')) => parse_array(s, chars),
Some((_, '"')) => parse_string(s, chars).map(Json::String),
Some((_, 't' | 'f')) => parse_bool(chars),
Some((_, 'n')) => parse_null(chars),
Some((_, '-' | '0'..='9')) => parse_number(chars),
Some(_) => Err("unexpected character"),
None => Err("unexpected end of input"),
}
}
fn parse_object<'a>(s: &'a str, chars: &mut Peekable<CharIndices>) -> Result<Json<'a>, &'static str> {
chars.next(); // consume '{'
let mut map = HashMap::new();
skip_whitespace(chars);
if matches!(chars.peek(), Some((_, '}'))) {
chars.next();
return Ok(Json::Object(map));
}
loop {
skip_whitespace(chars);
let key = parse_string(s, chars)?;
skip_whitespace(chars);
if !matches!(chars.next(), Some((_, ':'))) {
return Err("expected ':'");
}
let value = parse_value(s, chars)?;
map.insert(key, value);
skip_whitespace(chars);
match chars.next() {
Some((_, ',')) => continue,
Some((_, '}')) => break,
_ => return Err("expected ',' or '}'"),
}
}
Ok(Json::Object(map))
}
fn parse_array<'a>(s: &'a str, chars: &mut Peekable<CharIndices>) -> Result<Json<'a>, &'static str> {
chars.next(); // consume '['
let mut arr = Vec::new();
skip_whitespace(chars);
if matches!(chars.peek(), Some((_, ']'))) {
chars.next();
return Ok(Json::Array(arr));
}
loop {
skip_whitespace(chars);
arr.push(parse_value(s, chars)?);
skip_whitespace(chars);
match chars.next() {
Some((_, ',')) => continue,
Some((_, ']')) => return Ok(Json::Array(arr)),
_ => return Err("expected ',' or ']'"),
}
}
}
fn parse_string<'a>(s: &'a str, chars: &mut Peekable<CharIndices>) -> Result<&'a str, &'static str> {
if !matches!(chars.next(), Some((_, '"'))) {
return Err("expected string starting with '\"'");
}
let start = chars.peek().map(|(i, _)| *i).unwrap_or(0);
for (i, c) in chars {
if c == '"' {
return Ok(&s[start..i]);
}
}
Err("unterminated string")
}
fn parse_number(chars: &mut Peekable<CharIndices>) -> Result<Json<'static>, &'static str> {
let mut is_float = false;
let mut num = String::new();
while let Some((_, c @ ('0'..='9' | '-' | '.' | 'e' | 'E' | '+'))) = chars.peek() {
num.push(*c);
is_float = is_float || matches!(c, '.' | 'e' | 'E');
chars.next();
}
if !is_float {
let i = num.parse::<i64>().map_err(|_| "invalid integer")?;
return Ok(Json::Integer(i));
}
let n = num.parse::<f64>().map_err(|_| "invalid number")?;
Ok(Json::Number(n))
}
fn parse_bool(chars: &mut Peekable<CharIndices>) -> Result<Json<'static>, &'static str> {
let bool = next_chars(chars);
if bool == [Some('t'), Some('r'), Some('u'), Some('e')] {
return Ok(Json::Bool(true));
}
if bool == [Some('f'), Some('a'), Some('l'), Some('s')] && matches!(chars.next(), Some((_, 'e'))) {
return Ok(Json::Bool(false));
}
Err("invalid boolean literal")
}
fn parse_null(chars: &mut Peekable<CharIndices>) -> Result<Json<'static>, &'static str> {
if next_chars(chars) == [Some('n'), Some('u'), Some('l'), Some('l')] {
return Ok(Json::Null);
}
Err("invalid \"null\" literal")
}
fn skip_whitespace(chars: &mut Peekable<CharIndices>) {
while let Some((_, ' ' | '\n' | '\r' | '\t')) = chars.peek() {
chars.next();
}
}
fn next_chars<const N: usize>(chars: &mut Peekable<CharIndices>) -> [Option<char>; N] {
array::from_fn(|_| chars.next().map(|(_, c)| c))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse() {
assert_eq!(parse("null").unwrap(), Json::Null);
assert_eq!(parse("true").unwrap(), Json::Bool(true));
assert_eq!(parse("false").unwrap(), Json::Bool(false));
assert_eq!(parse("42").unwrap(), Json::Integer(42));
assert_eq!(parse("42.0").unwrap(), Json::Number(42.0));
assert_eq!(parse(r#""hello""#).unwrap(), Json::String("hello"));
assert_eq!(
parse("[1,2.0,3]").unwrap(),
Json::Array(vec![Json::Integer(1), Json::Number(2.0), Json::Integer(3)])
);
let mut obj = HashMap::new();
obj.insert("key", Json::String("value"));
assert_eq!(parse(r#"{"key":"value"}"#).unwrap(), Json::Object(obj));
}
#[test]
fn test_whitespace_handling() {
assert_eq!(parse(" null ").unwrap(), Json::Null);
assert_eq!(parse(" true ").unwrap(), Json::Bool(true));
assert_eq!(
parse(" [ 1 , 2.0 , 3 ] ").unwrap(),
Json::Array(vec![Json::Integer(1), Json::Number(2.0), Json::Integer(3)])
);
let mut obj = HashMap::new();
obj.insert("key", Json::String("value"));
assert_eq!(parse(r#" { "key" : "value" } "#).unwrap(), Json::Object(obj));
}
#[test]
fn test_empty_collections() {
assert_eq!(parse("[]").unwrap(), Json::Array(vec![]));
assert_eq!(parse("{}").unwrap(), Json::Object(HashMap::new()));
assert_eq!(parse("[ ]").unwrap(), Json::Array(vec![]));
assert_eq!(parse("{ }").unwrap(), Json::Object(HashMap::new()));
}
#[test]
fn test_nested_structures() {
assert_eq!(
parse(r#"{"nested":{"inner":"value"}}"#).unwrap(),
Json::Object({
let mut outer = HashMap::new();
let mut inner = HashMap::new();
inner.insert("inner", Json::String("value"));
outer.insert("nested", Json::Object(inner));
outer
})
);
assert_eq!(
parse("[[1,2],[3,4]]").unwrap(),
Json::Array(vec![
Json::Array(vec![Json::Integer(1), Json::Integer(2)]),
Json::Array(vec![Json::Integer(3), Json::Integer(4)])
])
);
}
#[test]
fn test_numbers() {
assert_eq!(parse("0").unwrap(), Json::Integer(0));
assert_eq!(parse("-42").unwrap(), Json::Integer(-42));
assert_eq!(parse("3.14").unwrap(), Json::Number(3.14));
assert_eq!(parse("-3.14").unwrap(), Json::Number(-3.14));
assert_eq!(parse("1e10").unwrap(), Json::Number(1e10));
assert_eq!(parse("1E10").unwrap(), Json::Number(1E10));
assert_eq!(parse("1e-10").unwrap(), Json::Number(1e-10));
assert_eq!(parse("1.5e+10").unwrap(), Json::Number(1.5e+10));
}
#[test]
fn test_strings() {
assert_eq!(parse(r#""""#).unwrap(), Json::String(""));
assert_eq!(parse(r#""hello world""#).unwrap(), Json::String("hello world"));
assert_eq!(
parse(r#""with spaces and 123""#).unwrap(),
Json::String("with spaces and 123")
);
}
#[test]
fn test_mixed_array() {
assert_eq!(
parse(r#"[null, true, false, 35.1, 42, "text", [], {}]"#).unwrap(),
Json::Array(vec![
Json::Null,
Json::Bool(true),
Json::Bool(false),
Json::Number(35.1),
Json::Integer(42),
Json::String("text"),
Json::Array(vec![]),
Json::Object(HashMap::new())
])
);
}
#[test]
fn test_object_multiple_keys() {
let mut obj = HashMap::new();
obj.insert("a", Json::Integer(1));
obj.insert("b", Json::Bool(true));
obj.insert("c", Json::Null);
assert_eq!(parse(r#"{"a":1,"b":true,"c":null}"#).unwrap(), Json::Object(obj));
}
#[test]
fn test_error_cases() {
assert!(parse("").is_err());
assert!(parse("nul").is_err());
assert!(parse("tru").is_err()); // typos:ignore
assert!(parse("fals").is_err()); // typos:ignore
assert!(parse(r#""unterminated"#).is_err());
assert!(parse("[1,2,]").is_err());
assert!(parse(r#"{"key""#).is_err());
assert!(parse(r#"{"key":"value""#).is_err());
assert!(parse(r#"{"key":"value",}"#).is_err());
assert!(parse("invalid").is_err());
assert!(parse("[1 2]").is_err());
assert!(parse(r#"{"key":"value" "key2":"value2"}"#).is_err());
}
}
+85 -10
View File
@@ -1,11 +1,16 @@
use std::ffi::CStr; use std::ffi::{CStr, CString};
use std::os::raw::c_int; use std::os::raw::c_int;
use std::ptr;
use crate::error::Result; use crate::chunk::ChunkMode;
use crate::error::{Error, Result};
use crate::function::Function; use crate::function::Function;
use crate::state::Lua; use crate::state::{ExtraData, Lua, callback_error_ext};
use crate::traits::{FromLuaMulti, IntoLua};
use crate::types::MaybeSend;
pub use require::{NavigateError, Require}; pub use heap_dump::HeapDump;
pub use require::{NavigateError, Require, TextRequirer};
// Since Luau has some missing standard functions, we re-implement them here // Since Luau has some missing standard functions, we re-implement them here
@@ -14,14 +19,65 @@ impl Lua {
/// and load modules. /// and load modules.
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn create_require_function<R: Require + 'static>(&self, require: R) -> Result<Function> { pub fn create_require_function<R: Require + MaybeSend + 'static>(&self, require: R) -> Result<Function> {
require::create_require_function(self, require) require::create_require_function(self, require)
} }
/// Set the memory category for subsequent allocations from this Lua state.
///
/// The category "main" is reserved for the default memory category.
/// Maximum of 255 categories can be registered.
/// The category is set per Lua thread (state) and affects all allocations made from that
/// thread.
///
/// Return error if too many categories are registered or if the category name is invalid.
///
/// See [`Lua::heap_dump`] for tracking memory usage by category.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_memory_category(&self, category: &str) -> Result<()> {
let lua = self.lock();
if category.contains(|c| !matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_')) {
return Err(Error::runtime("invalid memory category name"));
}
let cat_id = unsafe {
let extra = ExtraData::get(lua.state());
match ((*extra).mem_categories.iter().enumerate())
.find(|&(_, name)| name.as_bytes() == category.as_bytes())
{
Some((id, _)) => id as u8,
None => {
let new_id = (*extra).mem_categories.len() as u8;
if new_id == 255 {
return Err(Error::runtime("too many memory categories registered"));
}
(*extra).mem_categories.push(CString::new(category).unwrap());
new_id
}
}
};
unsafe { ffi::lua_setmemcat(lua.state(), cat_id as i32) };
Ok(())
}
/// Dumps the current Lua VM heap state.
///
/// The returned `HeapDump` can be used to analyze memory usage.
/// It's recommended to call [`Lua::gc_collect`] before dumping the heap.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn heap_dump(&self) -> Result<HeapDump> {
let lua = self.lock();
unsafe { heap_dump::HeapDump::new(lua.state()).ok_or_else(|| Error::runtime("unable to dump heap")) }
}
pub(crate) unsafe fn configure_luau(&self) -> Result<()> { pub(crate) unsafe fn configure_luau(&self) -> Result<()> {
let globals = self.globals(); let globals = self.globals();
globals.raw_set("collectgarbage", self.create_c_function(lua_collectgarbage)?)?; globals.raw_set("collectgarbage", self.create_c_function(lua_collectgarbage)?)?;
globals.raw_set("loadstring", self.create_c_function(lua_loadstring)?)?;
// Set `_VERSION` global to include version number // Set `_VERSION` global to include version number
// The environment variable `LUAU_VERSION` set by the build script // The environment variable `LUAU_VERSION` set by the build script
@@ -41,16 +97,17 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
let option = ffi::luaL_optstring(state, 1, cstr!("collect")); let option = ffi::luaL_optstring(state, 1, cstr!("collect"));
let option = CStr::from_ptr(option); let option = CStr::from_ptr(option);
let arg = ffi::luaL_optinteger(state, 2, 0); let arg = ffi::luaL_optinteger(state, 2, 0);
let is_sandboxed = (*ExtraData::get(state)).sandboxed;
match option.to_str() { match option.to_str() {
Ok("collect") => { Ok("collect") if !is_sandboxed => {
ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0); ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0);
0 0
} }
Ok("stop") => { Ok("stop") if !is_sandboxed => {
ffi::lua_gc(state, ffi::LUA_GCSTOP, 0); ffi::lua_gc(state, ffi::LUA_GCSTOP, 0);
0 0
} }
Ok("restart") => { Ok("restart") if !is_sandboxed => {
ffi::lua_gc(state, ffi::LUA_GCRESTART, 0); ffi::lua_gc(state, ffi::LUA_GCRESTART, 0);
0 0
} }
@@ -60,12 +117,12 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
ffi::lua_pushnumber(state, kbytes + kbytes_rem / 1024.0); ffi::lua_pushnumber(state, kbytes + kbytes_rem / 1024.0);
1 1
} }
Ok("step") => { Ok("step") if !is_sandboxed => {
let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg as _); let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg as _);
ffi::lua_pushboolean(state, res); ffi::lua_pushboolean(state, res);
1 1
} }
Ok("isrunning") => { Ok("isrunning") if !is_sandboxed => {
let res = ffi::lua_gc(state, ffi::LUA_GCISRUNNING, 0); let res = ffi::lua_gc(state, ffi::LUA_GCISRUNNING, 0);
ffi::lua_pushboolean(state, res); ffi::lua_pushboolean(state, res);
1 1
@@ -74,4 +131,22 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
} }
} }
unsafe extern "C-unwind" fn lua_loadstring(state: *mut ffi::lua_State) -> c_int {
callback_error_ext(state, ptr::null_mut(), false, move |extra, nargs| {
let rawlua = (*extra).raw_lua();
let (chunk, chunk_name) =
<(String, Option<String>)>::from_stack_args(nargs, 1, Some("loadstring"), rawlua)?;
let chunk_name = chunk_name.as_deref().unwrap_or("=(loadstring)");
(rawlua.lua())
.load(chunk)
.set_name(chunk_name)
.set_mode(ChunkMode::Text)
.into_function()?
.push_into_stack(rawlua)?;
Ok(1)
})
}
mod heap_dump;
mod json;
mod require; mod require;
+230 -403
View File
@@ -1,98 +1,100 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::VecDeque;
use std::ffi::CStr; use std::ffi::CStr;
use std::io::Result as IoResult; use std::io::Result as IoResult;
use std::ops::{Deref, DerefMut};
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::path::{Component, Path, PathBuf};
use std::result::Result as StdResult; use std::result::Result as StdResult;
use std::{env, fmt, fs, mem, ptr}; use std::{fmt, mem, ptr};
use crate::error::Result; use crate::error::{Error, Result};
use crate::function::Function; use crate::function::Function;
use crate::state::{callback_error_ext, Lua}; use crate::state::{Lua, callback_error_ext};
use crate::table::Table; use crate::table::Table;
use crate::types::MaybeSend; use crate::types::MaybeSend;
/// An error that can occur during navigation in the Luau `require` system. // TODO: Rename to FsRequirer
pub use fs::TextRequirer;
/// An error that can occur during navigation in the Luau `require-by-string` system.
#[derive(Debug, Clone)]
pub enum NavigateError { pub enum NavigateError {
Ambiguous, Ambiguous,
NotFound, NotFound,
Other(Error),
} }
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
trait IntoNavigateResult { trait IntoNavigateResult {
fn into_nav_result(self) -> ffi::luarequire_NavigateResult; fn into_nav_result(self) -> Result<ffi::luarequire_NavigateResult>;
} }
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
impl IntoNavigateResult for StdResult<(), NavigateError> { impl IntoNavigateResult for StdResult<(), NavigateError> {
fn into_nav_result(self) -> ffi::luarequire_NavigateResult { fn into_nav_result(self) -> Result<ffi::luarequire_NavigateResult> {
match self { match self {
Ok(()) => ffi::luarequire_NavigateResult::Success, Ok(()) => Ok(ffi::luarequire_NavigateResult::Success),
Err(NavigateError::Ambiguous) => ffi::luarequire_NavigateResult::Ambiguous, Err(NavigateError::Ambiguous) => Ok(ffi::luarequire_NavigateResult::Ambiguous),
Err(NavigateError::NotFound) => ffi::luarequire_NavigateResult::NotFound, Err(NavigateError::NotFound) => Ok(ffi::luarequire_NavigateResult::NotFound),
Err(NavigateError::Other(err)) => Err(err),
} }
} }
} }
impl From<Error> for NavigateError {
fn from(err: Error) -> Self {
NavigateError::Other(err)
}
}
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
type WriteResult = ffi::luarequire_WriteResult; type WriteResult = ffi::luarequire_WriteResult;
/// A trait for handling modules loading and navigation in the Luau `require` system. #[cfg(feature = "luau")]
pub trait Require: MaybeSend { type ConfigStatus = ffi::luarequire_ConfigStatus;
/// A trait for handling modules loading and navigation in the Luau `require-by-string` system.
pub trait Require {
/// Returns `true` if "require" is permitted for the given chunk name. /// Returns `true` if "require" is permitted for the given chunk name.
fn is_require_allowed(&self, chunk_name: &str) -> bool; fn is_require_allowed(&self, chunk_name: &str) -> bool;
/// Resets the internal state to point at the requirer module. /// Resets the internal state to point at the requirer module.
fn reset(&self, chunk_name: &str) -> StdResult<(), NavigateError>; fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError>;
/// Resets the internal state to point at an aliased module. /// Resets the internal state to point at an aliased module.
/// ///
/// This function received an exact path from a configuration file. /// 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 /// It's only called when an alias's path cannot be resolved relative to its
/// configuration file. /// configuration file.
fn jump_to_alias(&self, path: &str) -> StdResult<(), NavigateError>; fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError>;
// Navigate to parent directory // Navigate to parent directory
fn to_parent(&self) -> StdResult<(), NavigateError>; fn to_parent(&mut self) -> StdResult<(), NavigateError>;
/// Navigate to the given child directory. /// Navigate to the given child directory.
fn to_child(&self, name: &str) -> StdResult<(), NavigateError>; fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError>;
/// Returns whether the context is currently pointing at a module /// Returns whether the context is currently pointing at a module.
fn is_module_present(&self) -> bool; fn has_module(&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. /// Provides a cache key representing the current module.
/// ///
/// This function is only called if `is_module_present` returns true. /// This function is only called if `has_module` returns true.
fn cache_key(&self) -> Vec<u8>; fn cache_key(&self) -> String;
/// Returns whether a configuration file is present in the current context. /// Returns whether a configuration is present in the current context.
fn is_config_present(&self) -> bool; fn has_config(&self) -> bool;
/// Returns the contents of the configuration file in the current context. /// Returns the contents of the configuration file in the current context.
/// ///
/// This function is only called if `is_config_present` returns true. /// This function is only called if `has_config` returns true.
fn config(&self) -> IoResult<Vec<u8>>; fn config(&self) -> IoResult<Vec<u8>>;
/// Returns a loader that when called, loads the module and returns the result. /// Returns a loader function for the current module, that when called, loads the module
/// and returns the result.
/// ///
/// Loader can be sync or async. /// Loader can be sync or async.
fn loader(&self, lua: &Lua, path: &str, chunk_name: &str, content: &[u8]) -> Result<Function> { /// This function is only called if `has_module` returns true.
let _ = path; fn loader(&self, lua: &Lua) -> Result<Function>;
lua.load(content).set_name(chunk_name).into_function()
}
} }
impl fmt::Debug for dyn Require { impl fmt::Debug for dyn Require {
@@ -101,213 +103,60 @@ impl fmt::Debug for dyn Require {
} }
} }
/// The standard implementation of Luau `require` navigation. struct Context {
#[derive(Default)] require: Box<dyn Require>,
pub(super) struct TextRequirer { config_cache: Option<IoResult<Vec<u8>>>,
abs_path: RefCell<PathBuf>,
rel_path: RefCell<PathBuf>,
module_path: RefCell<PathBuf>,
} }
impl TextRequirer { impl Deref for Context {
pub(super) fn new() -> Self { type Target = dyn Require;
Self::default()
}
fn normalize_chunk_name(chunk_name: &str) -> &str { fn deref(&self) -> &Self::Target {
if let Some((path, line)) = chunk_name.split_once(':') { &*self.require
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 { impl DerefMut for Context {
fn is_require_allowed(&self, chunk_name: &str) -> bool { fn deref_mut(&mut self) -> &mut Self::Target {
chunk_name.starts_with('@') &mut *self.require
} }
}
fn reset(&self, chunk_name: &str) -> StdResult<(), NavigateError> { impl Context {
if !chunk_name.starts_with('@') { fn new(require: impl Require + MaybeSend + 'static) -> Self {
return Err(NavigateError::NotFound); Context {
require: Box::new(require),
config_cache: None,
} }
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()) { macro_rules! try_borrow {
let cwd = match env::current_dir() { ($state:expr, $ctx:expr) => {
Ok(cwd) => cwd, match (*($ctx as *const RefCell<Context>)).try_borrow() {
Err(_) => return Err(NavigateError::NotFound), Ok(ctx) => ctx,
}; Err(_) => ffi::luaL_error($state, cstr!("require context is already borrowed")),
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() { macro_rules! try_borrow_mut {
let module_path = Self::find_module_path(&path)?; ($state:expr, $ctx:expr) => {
self.abs_path.replace(path.clone()); match (*($ctx as *const RefCell<Context>)).try_borrow_mut() {
self.rel_path.replace(path); Ok(ctx) => ctx,
self.module_path.replace(module_path); Err(_) => ffi::luaL_error($state, cstr!("require context is already borrowed")),
} 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")] #[cfg(feature = "luau")]
pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configuration) { pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_Configuration) {
if config.is_null() { if config.is_null() {
return; return;
} }
unsafe extern "C" fn is_require_allowed( unsafe extern "C-unwind" fn is_require_allowed(
_state: *mut ffi::lua_State, state: *mut ffi::lua_State,
ctx: *mut c_void, ctx: *mut c_void,
requirer_chunkname: *const c_char, requirer_chunkname: *const c_char,
) -> bool { ) -> bool {
@@ -315,119 +164,134 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu
return false; return false;
} }
let this = &*(ctx as *const Box<dyn Require>); let this = try_borrow!(state, ctx);
let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy(); let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy();
this.is_require_allowed(&chunk_name) this.is_require_allowed(&chunk_name)
} }
unsafe extern "C" fn reset( unsafe extern "C-unwind" fn reset(
_state: *mut ffi::lua_State, state: *mut ffi::lua_State,
ctx: *mut c_void, ctx: *mut c_void,
requirer_chunkname: *const c_char, requirer_chunkname: *const c_char,
) -> ffi::luarequire_NavigateResult { ) -> ffi::luarequire_NavigateResult {
let this = &*(ctx as *const Box<dyn Require>); let mut this = try_borrow_mut!(state, ctx);
let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy(); let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy();
this.reset(&chunk_name).into_nav_result() callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
} 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( unsafe extern "C-unwind" fn jump_to_alias(
state: *mut ffi::lua_State,
ctx: *mut c_void,
path: *const c_char,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
let path = CStr::from_ptr(path).to_string_lossy();
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.jump_to_alias(&path).into_nav_result()
})
}
unsafe extern "C-unwind" fn to_parent(
state: *mut ffi::lua_State,
ctx: *mut c_void,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.to_parent().into_nav_result()
})
}
unsafe extern "C-unwind" fn to_child(
state: *mut ffi::lua_State,
ctx: *mut c_void,
name: *const c_char,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
let name = CStr::from_ptr(name).to_string_lossy();
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.to_child(&name).into_nav_result()
})
}
unsafe extern "C-unwind" fn is_module_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool {
let this = try_borrow!(state, ctx);
this.has_module()
}
unsafe extern "C-unwind" fn get_chunkname(
_state: *mut ffi::lua_State,
_ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
write_to_buffer(buffer, buffer_size, size_out, &[])
}
unsafe extern "C-unwind" fn get_loadname(
_state: *mut ffi::lua_State,
_ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
write_to_buffer(buffer, buffer_size, size_out, &[])
}
unsafe extern "C-unwind" fn get_cache_key(
state: *mut ffi::lua_State, state: *mut ffi::lua_State,
ctx: *mut c_void, ctx: *mut c_void,
buffer: *mut c_char, buffer: *mut c_char,
buffer_size: usize, buffer_size: usize,
size_out: *mut usize, size_out: *mut usize,
) -> WriteResult { ) -> WriteResult {
let this = &*(ctx as *const Box<dyn Require>); let this = try_borrow!(state, ctx);
write_to_buffer(state, buffer, buffer_size, size_out, || Ok(this.cache_key())) let cache_key = this.cache_key();
write_to_buffer(buffer, buffer_size, size_out, cache_key.as_bytes())
} }
unsafe extern "C" fn is_config_present(_state: *mut ffi::lua_State, ctx: *mut c_void) -> bool { unsafe extern "C-unwind" fn get_config_status(
let this = &*(ctx as *const Box<dyn Require>); state: *mut ffi::lua_State,
this.is_config_present() ctx: *mut c_void,
) -> ConfigStatus {
let mut this = try_borrow_mut!(state, ctx);
if this.has_config() {
this.config_cache = Some(this.config());
if let Some(Ok(data)) = &this.config_cache {
return detect_config_format(data);
}
}
ConfigStatus::Absent
} }
unsafe extern "C" fn get_config( unsafe extern "C-unwind" fn get_config(
state: *mut ffi::lua_State, state: *mut ffi::lua_State,
ctx: *mut c_void, ctx: *mut c_void,
buffer: *mut c_char, buffer: *mut c_char,
buffer_size: usize, buffer_size: usize,
size_out: *mut usize, size_out: *mut usize,
) -> WriteResult { ) -> WriteResult {
let this = &*(ctx as *const Box<dyn Require>); let mut this = try_borrow_mut!(state, ctx);
write_to_buffer(state, buffer, buffer_size, size_out, || this.config()) let config = callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
Ok(this.config_cache.take().unwrap_or_else(|| this.config())?)
});
write_to_buffer(buffer, buffer_size, size_out, &config)
} }
unsafe extern "C-unwind" fn load( unsafe extern "C-unwind" fn load(
state: *mut ffi::lua_State, state: *mut ffi::lua_State,
ctx: *mut c_void, ctx: *mut c_void,
path: *const c_char, _path: *const c_char,
chunk_name: *const c_char, _chunkname: *const c_char,
contents: *const c_char, _loadname: *const c_char,
) -> c_int { ) -> c_int {
let this = &*(ctx as *const Box<dyn Require>); let this = try_borrow!(state, ctx);
let path = CStr::from_ptr(path).to_string_lossy(); callback_error_ext(state, ptr::null_mut(), true, move |extra, _| {
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(); let rawlua = (*extra).raw_lua();
rawlua.push(this.loader(rawlua.lua(), &path, &chunk_name, contents)?)?; let loader = this.loader(rawlua.lua())?;
rawlua.push(loader)?;
Ok(1) Ok(1)
}) })
} }
@@ -435,67 +299,59 @@ pub(super) unsafe extern "C" fn init_config(config: *mut ffi::luarequire_Configu
(*config).is_require_allowed = is_require_allowed; (*config).is_require_allowed = is_require_allowed;
(*config).reset = reset; (*config).reset = reset;
(*config).jump_to_alias = jump_to_alias; (*config).jump_to_alias = jump_to_alias;
(*config).to_alias_override = None;
(*config).to_alias_fallback = None;
(*config).to_parent = to_parent; (*config).to_parent = to_parent;
(*config).to_child = to_child; (*config).to_child = to_child;
(*config).is_module_present = is_module_present; (*config).is_module_present = is_module_present;
(*config).get_contents = get_contents;
(*config).get_chunkname = get_chunkname; (*config).get_chunkname = get_chunkname;
(*config).get_loadname = get_loadname;
(*config).get_cache_key = get_cache_key; (*config).get_cache_key = get_cache_key;
(*config).is_config_present = is_config_present; (*config).get_config_status = get_config_status;
(*config).get_config = get_config; (*config).get_alias = None;
(*config).get_config = Some(get_config);
(*config).load = load; (*config).load = load;
} }
/// Detect configuration file format (JSON or Luau)
#[cfg(feature = "luau")]
fn detect_config_format(data: &[u8]) -> ConfigStatus {
let data = data.trim_ascii();
if data.starts_with(b"{") {
let data = &data[1..].trim_ascii_start();
if data.starts_with(b"\"") || data == b"}" {
return ConfigStatus::PresentJson;
}
}
ConfigStatus::PresentLuau
}
/// Helper function to write data to a buffer /// Helper function to write data to a buffer
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
unsafe fn write_to_buffer( unsafe fn write_to_buffer(
state: *mut ffi::lua_State,
buffer: *mut c_char, buffer: *mut c_char,
buffer_size: usize, buffer_size: usize,
size_out: *mut usize, size_out: *mut usize,
data_fetcher: impl Fn() -> IoResult<Vec<u8>>, data: &[u8],
) -> WriteResult { ) -> WriteResult {
struct DataCache(Option<Vec<u8>>); // the buffer must be null terminated as it's a c++ `std::string` data() buffer
let is_null_terminated = data.last() == Some(&0);
// The initial buffer size can be too small, to avoid making a second data fetch call, *size_out = data.len() + if is_null_terminated { 0 } else { 1 };
// we cache the content in the first call, and then re-use it. if *size_out > buffer_size {
return WriteResult::BufferTooSmall;
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(_) => {}
} }
ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data.len());
match data_fetcher() { if !is_null_terminated {
Ok(data) => { *buffer.add(data.len()) = 0;
*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,
} }
WriteResult::Success
} }
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> Result<Function> { pub(super) fn create_require_function<R: Require + MaybeSend + 'static>(
lua: &Lua,
require: R,
) -> Result<Function> {
unsafe extern "C-unwind" fn find_current_file(state: *mut ffi::lua_State) -> c_int { unsafe extern "C-unwind" fn find_current_file(state: *mut ffi::lua_State) -> c_int {
let mut ar: ffi::lua_Debug = mem::zeroed(); let mut ar: ffi::lua_Debug = mem::zeroed();
for level in 2.. { for level in 2.. {
@@ -511,18 +367,20 @@ pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> R
} }
unsafe extern "C-unwind" fn get_cache_key(state: *mut ffi::lua_State) -> c_int { 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 ctx = ffi::lua_touserdata(state, ffi::lua_upvalueindex(1));
let cache_key = (*requirer).cache_key(); let ctx = try_borrow!(state, ctx);
let cache_key = ctx.cache_key();
ffi::lua_pushlstring(state, cache_key.as_ptr() as *const _, cache_key.len()); ffi::lua_pushlstring(state, cache_key.as_ptr() as *const _, cache_key.len());
1 1
} }
let (get_cache_key, find_current_file, proxyrequire, registered_modules, loader_cache) = unsafe { let (get_cache_key, find_current_file, proxyrequire, registered_modules, loader_cache) = unsafe {
lua.exec_raw::<(Function, Function, Function, Table, Table)>((), move |state| { lua.exec_raw::<(Function, Function, Function, Table, Table)>((), move |state| {
let requirer_ptr = ffi::lua_newuserdata_t::<Box<dyn Require>>(state, Box::new(require)); let context = Context::new(require);
let context_ptr = ffi::lua_newuserdata_t(state, RefCell::new(context));
ffi::lua_pushcclosured(state, get_cache_key, cstr!("get_cache_key"), 1); ffi::lua_pushcclosured(state, get_cache_key, cstr!("get_cache_key"), 1);
ffi::lua_pushcfunctiond(state, find_current_file, cstr!("find_current_file")); ffi::lua_pushcfunctiond(state, find_current_file, cstr!("find_current_file"));
ffi::luarequire_pushproxyrequire(state, init_config, requirer_ptr as *mut _); ffi::luarequire_pushproxyrequire(state, init_config, context_ptr as *mut _);
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_REGISTERED_MODULES_TABLE); ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_REGISTERED_MODULES_TABLE);
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("__MLUA_LOADER_CACHE")); ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("__MLUA_LOADER_CACHE"));
}) })
@@ -540,10 +398,26 @@ pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> R
1 1
} }
let (error, r#type) = unsafe { unsafe extern "C-unwind" fn to_lowercase(state: *mut ffi::lua_State) -> c_int {
lua.exec_raw::<(Function, Function)>((), move |state| { let s = ffi::luaL_checkstring(state, 1);
let s = CStr::from_ptr(s);
if !s.to_bytes().iter().any(|&c| c.is_ascii_uppercase()) {
// If the string does not contain any uppercase ASCII letters, return it as is
return 1;
}
callback_error_ext(state, ptr::null_mut(), true, |extra, _| {
let s = (s.to_bytes().iter())
.map(|&c| c.to_ascii_lowercase())
.collect::<bstr::BString>();
(*extra).raw_lua().push(s).map(|_| 1)
})
}
let (error, r#type, to_lowercase) = unsafe {
lua.exec_raw::<(Function, Function, Function)>((), move |state| {
ffi::lua_pushcfunctiond(state, error, cstr!("error")); ffi::lua_pushcfunctiond(state, error, cstr!("error"));
ffi::lua_pushcfunctiond(state, r#type, cstr!("type")); ffi::lua_pushcfunctiond(state, r#type, cstr!("type"));
ffi::lua_pushcfunctiond(state, to_lowercase, cstr!("to_lowercase"));
}) })
}?; }?;
@@ -556,6 +430,7 @@ pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> R
env.raw_set("LOADER_CACHE", loader_cache)?; env.raw_set("LOADER_CACHE", loader_cache)?;
env.raw_set("error", error)?; env.raw_set("error", error)?;
env.raw_set("type", r#type)?; env.raw_set("type", r#type)?;
env.raw_set("to_lowercase", to_lowercase)?;
lua.load( lua.load(
r#" r#"
@@ -565,7 +440,7 @@ pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> R
end end
-- Check if the module (path) is explicitly registered -- Check if the module (path) is explicitly registered
local maybe_result = REGISTERED_MODULES[path] local maybe_result = REGISTERED_MODULES[to_lowercase(path)]
if maybe_result ~= nil then if maybe_result ~= nil then
return maybe_result return maybe_result
end end
@@ -593,52 +468,4 @@ pub fn create_require_function<R: Require + 'static>(lua: &Lua, require: R) -> R
.into_function() .into_function()
} }
#[cfg(test)] mod fs;
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}"
);
}
}
}
+278
View File
@@ -0,0 +1,278 @@
use std::collections::VecDeque;
use std::io::Result as IoResult;
use std::path::{Component, Path, PathBuf};
use std::result::Result as StdResult;
use std::{env, fs};
use crate::error::Result;
use crate::function::Function;
use crate::state::Lua;
use super::{NavigateError, Require};
/// The standard implementation of Luau `require-by-string` navigation.
#[derive(Default, Debug)]
pub struct TextRequirer {
/// An absolute path to the current Luau module (not mapped to a physical file)
abs_path: PathBuf,
/// A relative path to the current Luau module (not mapped to a physical file)
rel_path: PathBuf,
/// A physical path to the current Luau module, which is a file or a directory with an
/// `init.lua(u)` file
resolved_path: Option<PathBuf>,
}
impl TextRequirer {
/// The prefix used for chunk names in the require system.
/// Only chunk names starting with this prefix are allowed to be used in `require`.
const CHUNK_PREFIX: &str = "@";
/// The file extensions that are considered valid for Luau modules.
const FILE_EXTENSIONS: &[&str] = &["luau", "lua"];
/// The filename for the JSON configuration file.
const LUAURC_CONFIG_FILENAME: &str = ".luaurc";
/// The filename for the Luau configuration file.
const LUAU_CONFIG_FILENAME: &str = ".config.luau";
/// Creates a new `TextRequirer` instance.
pub fn new() -> Self {
Self::default()
}
fn normalize_chunk_name(chunk_name: &str) -> &str {
if let Some((path, line)) = chunk_name.rsplit_once(':')
&& 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()
}
/// Resolve a Luau module path to a physical file or directory.
///
/// Empty directories without init files are considered valid as "intermediate" directories.
fn resolve_module(path: &Path) -> StdResult<Option<PathBuf>, NavigateError> {
let mut found_path = None;
if path.components().next_back() != Some(Component::Normal("init".as_ref())) {
let current_ext = (path.extension().and_then(|s| s.to_str()))
.map(|s| format!("{s}."))
.unwrap_or_default();
for ext in Self::FILE_EXTENSIONS {
let candidate = path.with_extension(format!("{current_ext}{ext}"));
if candidate.is_file() && found_path.replace(candidate).is_some() {
return Err(NavigateError::Ambiguous);
}
}
}
if path.is_dir() {
for component in Self::FILE_EXTENSIONS.iter().map(|ext| format!("init.{ext}")) {
let candidate = path.join(component);
if candidate.is_file() && found_path.replace(candidate).is_some() {
return Err(NavigateError::Ambiguous);
}
}
if found_path.is_none() {
// Directories without init files are considered valid "intermediate" path
return Ok(None);
}
}
Ok(Some(found_path.ok_or(NavigateError::NotFound)?))
}
}
impl Require for TextRequirer {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
chunk_name.starts_with(Self::CHUNK_PREFIX)
}
fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError> {
if !chunk_name.starts_with(Self::CHUNK_PREFIX) {
return Err(NavigateError::NotFound);
}
let chunk_name = Self::normalize_chunk_name(&chunk_name[1..]);
let chunk_path = Self::normalize_path(chunk_name.as_ref());
if chunk_path.extension() == Some("rs".as_ref()) {
// Special case for Rust source files, reset to the current directory
let chunk_filename = chunk_path.file_name().unwrap();
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
self.abs_path = Self::normalize_path(&cwd.join(chunk_filename));
self.rel_path = ([Component::CurDir, Component::Normal(chunk_filename)].into_iter()).collect();
self.resolved_path = None;
return Ok(());
}
if chunk_path.is_absolute() {
let resolved_path = Self::resolve_module(&chunk_path)?;
self.abs_path = chunk_path.clone();
self.rel_path = chunk_path;
self.resolved_path = resolved_path;
} else {
// Relative path
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
let abs_path = Self::normalize_path(&cwd.join(&chunk_path));
let resolved_path = Self::resolve_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = chunk_path;
self.resolved_path = resolved_path;
}
Ok(())
}
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> {
let path = Self::normalize_path(path.as_ref());
let resolved_path = Self::resolve_module(&path)?;
self.abs_path = path.clone();
self.rel_path = path;
self.resolved_path = resolved_path;
Ok(())
}
fn to_parent(&mut self) -> StdResult<(), NavigateError> {
let mut abs_path = self.abs_path.clone();
if !abs_path.pop() {
// It's important to return `NotFound` if we reached the root, as it's a "recoverable" error if we
// cannot go beyond the root directory.
// Luau "require-by-string` has a special logic to search for config file to resolve aliases.
return Err(NavigateError::NotFound);
}
let mut rel_parent = self.rel_path.clone();
rel_parent.pop();
let resolved_path = Self::resolve_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = Self::normalize_path(&rel_parent);
self.resolved_path = resolved_path;
Ok(())
}
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> {
let abs_path = self.abs_path.join(name);
let rel_path = self.rel_path.join(name);
let resolved_path = Self::resolve_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = rel_path;
self.resolved_path = resolved_path;
Ok(())
}
fn has_module(&self) -> bool {
(self.resolved_path.as_deref())
.map(Path::is_file)
.unwrap_or(false)
}
fn cache_key(&self) -> String {
self.resolved_path.as_deref().unwrap().display().to_string()
}
fn has_config(&self) -> bool {
self.abs_path.is_dir() && self.abs_path.join(Self::LUAURC_CONFIG_FILENAME).is_file()
|| self.abs_path.is_dir() && self.abs_path.join(Self::LUAU_CONFIG_FILENAME).is_file()
}
fn config(&self) -> IoResult<Vec<u8>> {
if self.abs_path.join(Self::LUAURC_CONFIG_FILENAME).is_file() {
return fs::read(self.abs_path.join(Self::LUAURC_CONFIG_FILENAME));
}
fs::read(self.abs_path.join(Self::LUAU_CONFIG_FILENAME))
}
fn loader(&self, lua: &Lua) -> Result<Function> {
let name = format!("@{}", self.rel_path.display());
lua.load(self.resolved_path.as_deref().unwrap())
.set_name(name)
.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 -14
View File
@@ -28,9 +28,7 @@ impl MemoryState {
} }
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
#[rustversion::since(1.85)]
#[inline] #[inline]
#[allow(clippy::incompatible_msrv)]
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
let mut mem_state = ptr::null_mut(); let mut mem_state = ptr::null_mut();
if !ptr::fn_addr_eq(ffi::lua_getallocf(state, &mut mem_state), ALLOCATOR) { if !ptr::fn_addr_eq(ffi::lua_getallocf(state, &mut mem_state), ALLOCATOR) {
@@ -39,17 +37,6 @@ impl MemoryState {
mem_state as *mut MemoryState mem_state as *mut MemoryState
} }
#[cfg(not(feature = "luau"))]
#[rustversion::before(1.85)]
#[inline]
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
let mut mem_state = ptr::null_mut();
if ffi::lua_getallocf(state, &mut mem_state) != ALLOCATOR {
mem_state = ptr::null_mut();
}
mem_state as *mut MemoryState
}
#[inline] #[inline]
pub(crate) fn used_memory(&self) -> usize { pub(crate) fn used_memory(&self) -> usize {
self.used_memory as usize self.used_memory as usize
@@ -83,7 +70,7 @@ impl MemoryState {
} }
// Does nothing apart from calling `f()`, we don't need to bypass any limits // Does nothing apart from calling `f()`, we don't need to bypass any limits
#[cfg(any(feature = "lua52", feature = "lua53", feature = "lua54"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
#[inline] #[inline]
pub(crate) unsafe fn relax_limit_with(_state: *mut ffi::lua_State, f: impl FnOnce()) { pub(crate) unsafe fn relax_limit_with(_state: *mut ffi::lua_State, f: impl FnOnce()) {
f(); f();
+27 -1
View File
@@ -1,4 +1,4 @@
use std::collections::{vec_deque, VecDeque}; use std::collections::{VecDeque, vec_deque};
use std::iter::FromIterator; use std::iter::FromIterator;
use std::mem; use std::mem;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
@@ -204,6 +204,23 @@ impl IntoLuaMulti for MultiValue {
} }
} }
impl IntoLuaMulti for &MultiValue {
#[inline]
fn into_lua_multi(self, _: &Lua) -> Result<MultiValue> {
Ok(self.clone())
}
#[inline]
unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result<c_int> {
let nresults = self.len() as i32;
check_stack(lua.state(), nresults + 1)?;
for value in &self.0 {
lua.push_value(value)?;
}
Ok(nresults)
}
}
impl FromLuaMulti for MultiValue { impl FromLuaMulti for MultiValue {
#[inline] #[inline]
fn from_lua_multi(values: MultiValue, _: &Lua) -> Result<Self> { fn from_lua_multi(values: MultiValue, _: &Lua) -> Result<Self> {
@@ -297,6 +314,15 @@ impl<T: IntoLua> IntoLuaMulti for Variadic<T> {
fn into_lua_multi(self, lua: &Lua) -> Result<MultiValue> { fn into_lua_multi(self, lua: &Lua) -> Result<MultiValue> {
MultiValue::from_lua_iter(lua, self) MultiValue::from_lua_iter(lua, self)
} }
unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result<c_int> {
let nresults = self.len() as i32;
check_stack(lua.state(), nresults + 1)?;
for value in self.0 {
value.push_into_stack(lua)?;
}
Ok(nresults)
}
} }
impl<T: FromLua> FromLuaMulti for Variadic<T> { impl<T: FromLua> FromLuaMulti for Variadic<T> {
+16 -14
View File
@@ -2,18 +2,19 @@
#[doc(no_inline)] #[doc(no_inline)]
pub use crate::{ pub use crate::{
AnyUserData as LuaAnyUserData, Chunk as LuaChunk, Either as LuaEither, Error as LuaError, AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr,
ErrorContext as LuaErrorContext, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, Chunk as LuaChunk, Either as LuaEither, Error as LuaError, ErrorContext as LuaErrorContext,
FromLua, FromLuaMulti, Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti,
Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, Function as LuaFunction, GCMode as LuaGCMode, Integer as LuaInteger, IntoLua, IntoLuaMulti,
LuaNativeFnMut, LuaOptions, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, LuaString,
Number as LuaNumber, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
StdLib as LuaStdLib, String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs, ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib,
TableSequence as LuaTableSequence, Thread as LuaThread, ThreadStatus as LuaThreadStatus, Table as LuaTable, Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData,
UserData as LuaUserData, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef, UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef,
UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue,
Variadic as LuaVariadic, VmState as LuaVmState, WeakLua, Variadic as LuaVariadic, VmState as LuaVmState, WeakLua, function::FunctionInfo as LuaFunctionInfo,
table::TablePairs as LuaTablePairs, table::TableSequence as LuaTableSequence,
}; };
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
@@ -23,16 +24,17 @@ pub use crate::HookTriggers as LuaHookTriggers;
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
#[doc(no_inline)] #[doc(no_inline)]
pub use crate::{ pub use crate::{
CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo, CompileConstant as LuaCompileConstant, NavigateError as LuaNavigateError, Require as LuaRequire,
NavigateError as LuaNavigateError, Require as LuaRequire, Vector as LuaVector, TextRequirer as LuaTextRequirer, Vector as LuaVector,
}; };
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[doc(no_inline)] #[doc(no_inline)]
pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn}; pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn};
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
#[doc(no_inline)] #[doc(no_inline)]
pub use crate::{ pub use crate::{
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializeOptions as LuaSerializeOptions, DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializableValue as LuaSerializableValue,
SerializeOptions as LuaSerializeOptions,
}; };
+4 -10
View File
@@ -8,9 +8,7 @@ use crate::state::{Lua, LuaGuard, RawLua};
use crate::traits::{FromLuaMulti, IntoLuaMulti}; use crate::traits::{FromLuaMulti, IntoLuaMulti};
use crate::types::{Callback, CallbackUpvalue, ScopedCallback, ValueRef}; use crate::types::{Callback, CallbackUpvalue, ScopedCallback, ValueRef};
use crate::userdata::{AnyUserData, UserData, UserDataRegistry, UserDataStorage}; use crate::userdata::{AnyUserData, UserData, UserDataRegistry, UserDataStorage};
use crate::util::{ use crate::util::{self, StackGuard, check_stack, get_metatable_ptr, get_userdata, take_userdata};
self, assert_stack, check_stack, get_metatable_ptr, get_userdata, take_userdata, StackGuard,
};
/// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and /// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and
/// callbacks that are not required to be `Send` or `'static`. /// callbacks that are not required to be `Send` or `'static`.
@@ -284,22 +282,18 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
/// Shortens the lifetime of the userdata to the lifetime of the scope. /// Shortens the lifetime of the userdata to the lifetime of the scope.
fn seal_userdata<T: 'env>(&self, ud: &AnyUserData) { fn seal_userdata<T: 'env>(&self, ud: &AnyUserData) {
let destructor: DestructorCallback = Box::new(|rawlua, vref| unsafe { let destructor: DestructorCallback = Box::new(|rawlua, vref| unsafe {
let state = rawlua.state();
let _sg = StackGuard::new(state);
assert_stack(state, 2);
// Ensure that userdata is not destructed // Ensure that userdata is not destructed
match rawlua.push_userdata_ref(&vref) { match rawlua.get_userdata_ref_type_id(&vref) {
Ok(Some(_)) => {} Ok(Some(_)) => {}
Ok(None) => { Ok(None) => {
// Deregister metatable // Deregister metatable
let mt_ptr = get_metatable_ptr(state, -1); let mt_ptr = get_metatable_ptr(rawlua.ref_thread(), vref.index);
rawlua.deregister_userdata_metatable(mt_ptr); rawlua.deregister_userdata_metatable(mt_ptr);
} }
Err(_) => return vec![], Err(_) => return vec![],
} }
let data = take_userdata::<UserDataStorage<T>>(state); let data = take_userdata::<UserDataStorage<T>>(rawlua.ref_thread(), vref.index);
vec![Box::new(move || drop(data))] vec![Box::new(move || drop(data))]
}); });
self.destructors.0.borrow_mut().push((ud.0.clone(), destructor)); self.destructors.0.borrow_mut().push((ud.0.clone(), destructor));
+48 -14
View File
@@ -4,7 +4,6 @@ use std::cell::RefCell;
use std::os::raw::c_void; use std::os::raw::c_void;
use std::rc::Rc; use std::rc::Rc;
use std::result::Result as StdResult; use std::result::Result as StdResult;
use std::string::String as StdString;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use serde::de::{self, IntoDeserializer}; use serde::de::{self, IntoDeserializer};
@@ -15,11 +14,12 @@ use crate::userdata::AnyUserData;
use crate::value::Value; use crate::value::Value;
/// A struct for deserializing Lua values into Rust values. /// A struct for deserializing Lua values into Rust values.
#[derive(Debug)] #[derive(Debug, Default)]
pub struct Deserializer { pub struct Deserializer {
value: Value, value: Value,
options: Options, options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>, visited: Rc<RefCell<FxHashSet<*const c_void>>>,
len: Option<usize>, // A length hint for sequences
} }
/// A struct with options to change default deserializer behavior. /// A struct with options to change default deserializer behavior.
@@ -54,6 +54,19 @@ pub struct Options {
/// ///
/// Default: **false** /// Default: **false**
pub encode_empty_tables_as_array: bool, pub encode_empty_tables_as_array: bool,
/// If true, enable detection of mixed tables.
///
/// A mixed table is a table that has both array-like and map-like entries or several borders.
/// See [`The Length Operator`] documentation for details about borders.
///
/// When this option is disabled, a table with a non-zero length (with one or more borders) will
/// be always encoded as an array.
///
/// Default: **false**
///
/// [`The Length Operator`]: https://www.lua.org/manual/5.4/manual.html#3.4.7
pub detect_mixed_tables: bool,
} }
impl Default for Options { impl Default for Options {
@@ -70,6 +83,7 @@ impl Options {
deny_recursive_tables: true, deny_recursive_tables: true,
sort_keys: false, sort_keys: false,
encode_empty_tables_as_array: false, encode_empty_tables_as_array: false,
detect_mixed_tables: false,
} }
} }
@@ -108,6 +122,15 @@ impl Options {
self.encode_empty_tables_as_array = enabled; self.encode_empty_tables_as_array = enabled;
self self
} }
/// Sets [`detect_mixed_tables`] option.
///
/// [`detect_mixed_tables`]: #structfield.detect_mixed_tables
#[must_use]
pub const fn detect_mixed_tables(mut self, enable: bool) -> Self {
self.detect_mixed_tables = enable;
self
}
} }
impl Deserializer { impl Deserializer {
@@ -121,7 +144,7 @@ impl Deserializer {
Deserializer { Deserializer {
value, value,
options, options,
visited: Rc::new(RefCell::new(FxHashSet::default())), ..Default::default()
} }
} }
@@ -130,8 +153,14 @@ impl Deserializer {
value, value,
options, options,
visited, visited,
..Default::default()
} }
} }
fn with_len(mut self, len: usize) -> Self {
self.len = Some(len);
self
}
} }
impl<'de> serde::Deserializer<'de> for Deserializer { impl<'de> serde::Deserializer<'de> for Deserializer {
@@ -155,17 +184,22 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
Ok(s) => visitor.visit_str(&s), Ok(s) => visitor.visit_str(&s),
Err(_) => visitor.visit_bytes(&s.as_bytes()), Err(_) => visitor.visit_bytes(&s.as_bytes()),
}, },
Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor), Value::Table(ref t) => {
Value::Table(ref t) if self.options.encode_empty_tables_as_array && t.is_empty() => { if let Some(len) = t.encode_as_array(self.options) {
self.deserialize_seq(visitor) self.with_len(len).deserialize_seq(visitor)
} else {
self.deserialize_map(visitor)
}
} }
Value::Table(_) => self.deserialize_map(visitor),
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(), Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(),
Value::UserData(ud) if ud.is_serializable() => { Value::UserData(ud) if ud.is_serializable() => {
serde_userdata(ud, |value| value.deserialize_any(visitor)) serde_userdata(ud, |value| value.deserialize_any(visitor))
} }
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
Value::Buffer(buf) => visitor.visit_bytes(unsafe { buf.as_slice() }), Value::Buffer(buf) => {
let lua = buf.0.lua.lock();
visitor.visit_bytes(buf.as_slice(&lua))
}
Value::Function(_) Value::Function(_)
| Value::Thread(_) | Value::Thread(_)
| Value::UserData(_) | Value::UserData(_)
@@ -208,14 +242,14 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
Value::Table(table) => { Value::Table(table) => {
let _guard = RecursionGuard::new(&table, &self.visited); let _guard = RecursionGuard::new(&table, &self.visited);
let mut iter = table.pairs::<StdString, Value>(); let mut iter = table.pairs::<String, Value>();
let (variant, value) = match iter.next() { let (variant, value) = match iter.next() {
Some(v) => v?, Some(v) => v?,
None => { None => {
return Err(de::Error::invalid_value( return Err(de::Error::invalid_value(
de::Unexpected::Map, de::Unexpected::Map,
&"map with a single key", &"map with a single key",
)) ));
} }
}; };
@@ -267,14 +301,14 @@ impl<'de> serde::Deserializer<'de> for Deserializer {
Value::Table(t) => { Value::Table(t) => {
let _guard = RecursionGuard::new(&t, &self.visited); let _guard = RecursionGuard::new(&t, &self.visited);
let len = t.raw_len(); let len = self.len.unwrap_or_else(|| t.raw_len());
let mut deserializer = SeqDeserializer { let mut deserializer = SeqDeserializer {
seq: t.sequence_values(), seq: t.sequence_values().with_len(len),
options: self.options, options: self.options,
visited: self.visited, visited: self.visited,
}; };
let seq = visitor.visit_seq(&mut deserializer)?; let seq = visitor.visit_seq(&mut deserializer)?;
if deserializer.seq.count() == 0 { if deserializer.seq.next().is_none() {
Ok(seq) Ok(seq)
} else { } else {
Err(de::Error::invalid_length(len, &"fewer elements in the table")) Err(de::Error::invalid_length(len, &"fewer elements in the table"))
@@ -586,7 +620,7 @@ impl<'de> de::MapAccess<'de> for MapDeserializer<'_> {
} }
struct EnumDeserializer { struct EnumDeserializer {
variant: StdString, variant: String,
value: Option<Value>, value: Option<Value>,
options: Options, options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>, visited: Rc<RefCell<FxHashSet<*const c_void>>>,
+1 -1
View File
@@ -13,7 +13,7 @@ use crate::util::check_stack;
use crate::value::Value; use crate::value::Value;
/// Trait for serializing/deserializing Lua values using Serde. /// Trait for serializing/deserializing Lua values using Serde.
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub trait LuaSerdeExt: Sealed { pub trait LuaSerdeExt: Sealed {
/// A special value (lightuserdata) to encode/decode optional (none) values. /// A special value (lightuserdata) to encode/decode optional (none) values.
/// ///
+8 -8
View File
@@ -1,6 +1,6 @@
//! Serialize a Rust data structure into Lua value. //! Serialize a Rust data structure into Lua value.
use serde::{ser, Serialize}; use serde::{Serialize, ser};
use super::LuaSerdeExt; use super::LuaSerdeExt;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
@@ -256,7 +256,7 @@ impl<'a> ser::Serializer for Serializer<'a> {
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> { fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
let table = self.lua.create_table_with_capacity(len.unwrap_or(0), 0)?; let table = self.lua.create_table_with_capacity(len.unwrap_or(0), 0)?;
if self.options.set_array_metatable { if self.options.set_array_metatable {
table.set_metatable(Some(self.lua.array_metatable())); table.set_metatable(Some(self.lua.array_metatable()))?;
} }
Ok(SerializeSeq::new(self.lua, table, self.options)) Ok(SerializeSeq::new(self.lua, table, self.options))
} }
@@ -529,12 +529,12 @@ impl ser::SerializeStruct for SerializeStruct<'_> {
fn end(self) -> Result<Value> { fn end(self) -> Result<Value> {
match self.inner { match self.inner {
Some(table @ Value::Table(_)) => Ok(table), Some(table @ Value::Table(_)) => Ok(table),
Some(value) if self.options.detect_serde_json_arbitrary_precision => { Some(value @ Value::String(_)) if self.options.detect_serde_json_arbitrary_precision => {
let number_s = value.as_str().expect("not an arbitrary precision number"); let number_s = value.to_string()?;
if number_s.contains(['.', 'e', 'E']) { if number_s.contains(['.', 'e', 'E'])
if let Ok(number) = number_s.parse().map(Value::Number) { && let Ok(number) = number_s.parse().map(Value::Number)
return Ok(number); {
} return Ok(number);
} }
Ok(number_s Ok(number_s
.parse() .parse()
+378 -149
View File
@@ -8,14 +8,14 @@ use std::result::Result as StdResult;
use std::{fmt, mem, ptr}; use std::{fmt, mem, ptr};
use crate::chunk::{AsChunk, Chunk}; use crate::chunk::{AsChunk, Chunk};
use crate::debug::Debug;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::function::Function; use crate::function::Function;
use crate::hook::Debug;
use crate::memory::MemoryState; use crate::memory::MemoryState;
use crate::multi::MultiValue; use crate::multi::MultiValue;
use crate::scope::Scope; use crate::scope::Scope;
use crate::stdlib::StdLib; use crate::stdlib::StdLib;
use crate::string::String; use crate::string::LuaString;
use crate::table::Table; use crate::table::Table;
use crate::thread::Thread; use crate::thread::Thread;
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
@@ -24,13 +24,11 @@ use crate::types::{
ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak, ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak,
}; };
use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage}; use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage};
use crate::util::{ use crate::util::{StackGuard, assert_stack, check_stack, protect_lua_closure, push_string, rawset_field};
assert_stack, check_stack, protect_lua_closure, push_string, push_table, rawset_field, StackGuard,
};
use crate::value::{Nil, Value}; use crate::value::{Nil, Value};
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
use crate::{hook::HookTriggers, types::HookKind}; use crate::{debug::HookTriggers, types::HookKind};
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
use crate::{buffer::Buffer, chunk::Compiler}; use crate::{buffer::Buffer, chunk::Compiler};
@@ -39,9 +37,10 @@ use crate::{buffer::Buffer, chunk::Compiler};
use { use {
crate::types::LightUserData, crate::types::LightUserData,
std::future::{self, Future}, std::future::{self, Future},
std::task::Poll,
}; };
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
use serde::Serialize; use serde::Serialize;
pub(crate) use extra::ExtraData; pub(crate) use extra::ExtraData;
@@ -74,8 +73,8 @@ pub(crate) struct LuaGuard(ArcReentrantMutexGuard<RawLua>);
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GCMode { pub enum GCMode {
Incremental, Incremental,
#[cfg(feature = "lua54")] #[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
Generational, Generational,
} }
@@ -250,7 +249,7 @@ impl Lua {
ffi::luaL_loadstring as _, ffi::luaL_loadstring as _,
ffi::luaL_openlibs as _, ffi::luaL_openlibs as _,
]); ]);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
{ {
_symbols.push(ffi::lua_getglobal as _); _symbols.push(ffi::lua_getglobal as _);
_symbols.push(ffi::lua_setglobal as _); _symbols.push(ffi::lua_setglobal as _);
@@ -338,6 +337,39 @@ impl Lua {
R::from_stack_multi(nresults, &lua) R::from_stack_multi(nresults, &lua)
} }
/// Runs callback with the inner RawLua value. It can be used to manually push and get values on
/// the stack.
///
/// This function is safe because all unsafe actions with RawLua can only be done with unsafe
///
/// # Example
/// ```
/// # use mlua::{Lua, Result, FromLua, IntoLua};
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// let n: i32 = {
/// let num = 11i32;
/// lua.exec_raw_lua(|lua| {
/// unsafe {
/// <i32 as IntoLua>::push_into_stack(num, lua)?;
/// }
///
/// let n = unsafe {
/// <i32 as FromLua>::from_stack(-1, lua)?
/// };
/// Result::Ok(n)
/// })
/// }?;
/// assert_eq!(n, 11);
/// # Ok(())
/// # }
/// ```
#[doc(hidden)]
pub fn exec_raw_lua<R>(&self, f: impl FnOnce(&RawLua) -> R) -> R {
let lua = self.lock();
f(&lua)
}
/// Loads the specified subset of the standard libraries into an existing Lua state. /// 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. /// Use the [`StdLib`] flags to specify the libraries you want to load.
@@ -360,6 +392,8 @@ impl Lua {
if cfg!(feature = "luau") && !modname.starts_with('@') { if cfg!(feature = "luau") && !modname.starts_with('@') {
return Err(Error::runtime("module name must begin with '@'")); return Err(Error::runtime("module name must begin with '@'"));
} }
#[cfg(feature = "luau")]
let modname = modname.to_ascii_lowercase();
unsafe { unsafe {
self.exec_raw::<()>(value, |state| { self.exec_raw::<()>(value, |state| {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, LOADED_MODULES_KEY); ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, LOADED_MODULES_KEY);
@@ -377,11 +411,11 @@ impl Lua {
/// ///
/// This is similar to setting the [`package.preload[modname]`] field. /// This is similar to setting the [`package.preload[modname]`] field.
/// ///
/// [`package.preload[modname]`]: https://www.lua.org/manual/5.4/manual.html#pdf-package.preload /// [`package.preload[modname]`]: <https://www.lua.org/manual/5.4/manual.html#pdf-package.preload>
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn preload_module(&self, modname: &str, func: Function) -> Result<()> { pub fn preload_module(&self, modname: &str, func: Function) -> Result<()> {
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
let preload = unsafe { let preload = unsafe {
self.exec_raw::<Option<Table>>((), |state| { self.exec_raw::<Option<Table>>((), |state| {
ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_PRELOAD_TABLE); ffi::lua_getfield(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_PRELOAD_TABLE);
@@ -488,6 +522,7 @@ impl Lua {
/// - Set globals to read-only (and activates safeenv) /// - Set globals to read-only (and activates safeenv)
/// - Setup local environment table that performs writes locally and proxies reads to the global /// - Setup local environment table that performs writes locally and proxies reads to the global
/// environment. /// environment.
/// - Allow only `count` mode in `collectgarbage` function.
/// ///
/// # Examples /// # Examples
/// ///
@@ -545,7 +580,7 @@ impl Lua {
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn set_global_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()> pub fn set_global_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
where where
F: Fn(&Lua, Debug) -> Result<VmState> + MaybeSend + 'static, F: Fn(&Lua, &Debug) -> Result<VmState> + MaybeSend + 'static,
{ {
let lua = self.lock(); let lua = self.lock();
unsafe { unsafe {
@@ -578,7 +613,7 @@ impl Lua {
/// # fn main() -> Result<()> { /// # fn main() -> Result<()> {
/// let lua = Lua::new(); /// let lua = Lua::new();
/// lua.set_hook(HookTriggers::EVERY_LINE, |_lua, debug| { /// lua.set_hook(HookTriggers::EVERY_LINE, |_lua, debug| {
/// println!("line {}", debug.curr_line()); /// println!("line {:?}", debug.current_line());
/// Ok(VmState::Continue) /// Ok(VmState::Continue)
/// }); /// });
/// ///
@@ -595,7 +630,7 @@ impl Lua {
#[cfg_attr(docsrs, doc(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) -> Result<()>
where where
F: Fn(&Lua, Debug) -> Result<VmState> + MaybeSend + 'static, F: Fn(&Lua, &Debug) -> Result<VmState> + MaybeSend + 'static,
{ {
let lua = self.lock(); let lua = self.lock();
unsafe { lua.set_thread_hook(lua.state(), HookKind::Thread(triggers, XRc::new(callback))) } unsafe { lua.set_thread_hook(lua.state(), HookKind::Thread(triggers, XRc::new(callback))) }
@@ -630,13 +665,13 @@ impl Lua {
/// ///
/// Any Luau code is guaranteed to call this handler "eventually" /// Any Luau code is guaranteed to call this handler "eventually"
/// (in practice this can happen at any function call or at any loop iteration). /// (in practice this can happen at any function call or at any loop iteration).
/// This is similar to `Lua::set_hook` but in more simplified form.
/// ///
/// The provided interrupt function can error, and this error will be propagated through /// The provided interrupt function can error, and this error will be propagated through
/// the Luau code that was executing at the time the interrupt was triggered. /// the Luau code that was executing at the time the interrupt was triggered.
/// Also this can be used to implement continuous execution limits by instructing Luau VM to /// Also this can be used to implement continuous execution limits by instructing Luau VM to
/// yield by returning [`VmState::Yield`]. /// yield by returning [`VmState::Yield`]. The yield will happen only at yieldable points
/// /// of execution (not across metamethod/C-call boundaries).
/// This is similar to [`Lua::set_hook`] but in more simplified form.
/// ///
/// # Example /// # Example
/// ///
@@ -694,7 +729,10 @@ impl Lua {
match result { match result {
VmState::Continue => {} VmState::Continue => {}
VmState::Yield => { VmState::Yield => {
ffi::lua_yield(state, 0); // We can yield only at yieldable points, otherwise ignore and continue
if ffi::lua_isyieldable(state) != 0 {
ffi::lua_yield(state, 0);
}
} }
} }
} }
@@ -809,15 +847,14 @@ impl Lua {
} }
/// Sets the warning function to be used by Lua to emit warnings. /// Sets the warning function to be used by Lua to emit warnings.
#[cfg(feature = "lua54")] #[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
pub fn set_warning_function<F>(&self, callback: F) pub fn set_warning_function<F>(&self, callback: F)
where where
F: Fn(&Lua, &str, bool) -> Result<()> + MaybeSend + 'static, F: Fn(&Lua, &str, bool) -> Result<()> + MaybeSend + 'static,
{ {
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::{c_char, c_void}; use std::os::raw::{c_char, c_void};
use std::string::String as StdString;
unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) { unsafe extern "C-unwind" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) {
let extra = ud as *mut ExtraData; let extra = ud as *mut ExtraData;
@@ -827,7 +864,7 @@ impl Lua {
if XRc::strong_count(&warn_callback) > 2 { if XRc::strong_count(&warn_callback) > 2 {
return Ok(()); return Ok(());
} }
let msg = StdString::from_utf8_lossy(CStr::from_ptr(msg).to_bytes()); let msg = String::from_utf8_lossy(CStr::from_ptr(msg).to_bytes());
warn_callback((*extra).lua(), &msg, tocont != 0) warn_callback((*extra).lua(), &msg, tocont != 0)
}); });
} }
@@ -842,8 +879,8 @@ impl Lua {
/// Removes warning function previously set by `set_warning_function`. /// Removes warning function previously set by `set_warning_function`.
/// ///
/// This function has no effect if a warning function was not previously set. /// This function has no effect if a warning function was not previously set.
#[cfg(feature = "lua54")] #[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
pub fn remove_warning_function(&self) { pub fn remove_warning_function(&self) {
let lua = self.lock(); let lua = self.lock();
unsafe { unsafe {
@@ -856,8 +893,8 @@ impl Lua {
/// ///
/// A message in a call with `incomplete` set to `true` should be continued in /// A message in a call with `incomplete` set to `true` should be continued in
/// another call to this function. /// another call to this function.
#[cfg(feature = "lua54")] #[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
pub fn warning(&self, msg: impl AsRef<str>, incomplete: bool) { pub fn warning(&self, msg: impl AsRef<str>, incomplete: bool) {
let msg = msg.as_ref(); let msg = msg.as_ref();
let mut bytes = vec![0; msg.len() + 1]; let mut bytes = vec![0; msg.len() + 1];
@@ -870,18 +907,16 @@ impl Lua {
} }
} }
/// Gets information about the interpreter runtime stack. /// Gets information about the interpreter runtime stack at the given level.
/// ///
/// This function returns [`Debug`] structure that can be used to get information about the /// This function calls callback `f`, passing the [`Debug`] structure that can be used to get
/// function executing at a given level. Level `0` is the current running function, whereas /// information about the function executing at a given level.
/// level `n+1` is the function that has called level `n` (except for tail calls, which do /// Level `0` is the current running function, whereas level `n+1` is the function that has
/// not count in the stack). /// called level `n` (except for tail calls, which do not count in the stack).
/// pub fn inspect_stack<R>(&self, level: usize, f: impl FnOnce(&Debug) -> R) -> Option<R> {
/// [`Debug`]: crate::hook::Debug
pub fn inspect_stack(&self, level: usize) -> Option<Debug> {
let lua = self.lock(); let lua = self.lock();
unsafe { unsafe {
let mut ar: ffi::lua_Debug = mem::zeroed(); let mut ar = mem::zeroed::<ffi::lua_Debug>();
let level = level as c_int; let level = level as c_int;
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
if ffi::lua_getstack(lua.state(), level, &mut ar) == 0 { if ffi::lua_getstack(lua.state(), level, &mut ar) == 0 {
@@ -891,7 +926,28 @@ impl Lua {
if ffi::lua_getinfo(lua.state(), level, cstr!(""), &mut ar) == 0 { if ffi::lua_getinfo(lua.state(), level, cstr!(""), &mut ar) == 0 {
return None; return None;
} }
Some(Debug::new_owned(lua, level, ar))
Some(f(&Debug::new(&lua, level, &mut ar)))
}
}
/// Creates a traceback of the call stack at the given level.
///
/// The `msg` parameter, if provided, is added at the beginning of the traceback.
/// The `level` parameter works the same way as in [`Lua::inspect_stack`].
pub fn traceback(&self, msg: Option<&str>, level: usize) -> Result<LuaString> {
let lua = self.lock();
unsafe {
check_stack(lua.state(), 3)?;
protect_lua!(lua.state(), 0, 1, |state| {
let msg = match msg {
Some(s) => ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len()),
None => ptr::null(),
};
// `protect_lua` adds it's own call frame, so we need to increase level by 1
ffi::luaL_traceback(state, state, msg, (level + 1) as c_int);
})?;
Ok(LuaString(lua.pop_ref()))
} }
} }
@@ -930,7 +986,13 @@ impl Lua {
} }
/// Returns `true` if the garbage collector is currently running automatically. /// Returns `true` if the garbage collector is currently running automatically.
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] #[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
pub fn gc_is_running(&self) -> bool { pub fn gc_is_running(&self) -> bool {
let lua = self.lock(); let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCISRUNNING, 0) != 0 } unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCISRUNNING, 0) != 0 }
@@ -995,8 +1057,12 @@ impl Lua {
let lua = self.lock(); let lua = self.lock();
let state = lua.main_state(); let state = lua.main_state();
unsafe { unsafe {
#[cfg(not(feature = "luau"))] #[cfg(feature = "lua55")]
return ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, pause);
#[cfg(not(any(feature = "lua55", feature = "luau")))]
return ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause); return ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause);
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
return ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause); return ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause);
} }
@@ -1010,7 +1076,18 @@ impl Lua {
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5 /// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int { pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int {
let lua = self.lock(); let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSETSTEPMUL, step_multiplier) } unsafe {
#[cfg(feature = "lua55")]
return ffi::lua_gc(
lua.main_state(),
ffi::LUA_GCPARAM,
ffi::LUA_GCPSTEPMUL,
step_multiplier,
);
#[cfg(not(feature = "lua55"))]
return ffi::lua_gc(lua.main_state(), ffi::LUA_GCSETSTEPMUL, step_multiplier);
}
} }
/// Changes the collector to incremental mode with the given parameters. /// Changes the collector to incremental mode with the given parameters.
@@ -1052,9 +1129,16 @@ impl Lua {
GCMode::Incremental GCMode::Incremental
} }
#[cfg(feature = "lua55")]
let prev_mode = unsafe {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, pause);
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPMUL, step_multiplier);
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPSIZE, step_size);
ffi::lua_gc(state, ffi::LUA_GCINC)
};
#[cfg(feature = "lua54")] #[cfg(feature = "lua54")]
let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_multiplier, step_size) }; let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_multiplier, step_size) };
#[cfg(feature = "lua54")] #[cfg(any(feature = "lua55", feature = "lua54"))]
match prev_mode { match prev_mode {
ffi::LUA_GCINC => GCMode::Incremental, ffi::LUA_GCINC => GCMode::Incremental,
ffi::LUA_GCGEN => GCMode::Generational, ffi::LUA_GCGEN => GCMode::Generational,
@@ -1068,11 +1152,19 @@ impl Lua {
/// can be found in the Lua 5.4 [documentation][lua_doc]. /// can be found in the Lua 5.4 [documentation][lua_doc].
/// ///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#2.5.2 /// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#2.5.2
#[cfg(feature = "lua54")] #[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode { pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode {
let lua = self.lock(); let lua = self.lock();
let state = lua.main_state(); let state = lua.main_state();
#[cfg(feature = "lua55")]
let prev_mode = unsafe {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMUL, minor_multiplier);
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMAJOR, major_multiplier);
// TODO: LUA_GCPMAJORMINOR
ffi::lua_gc(state, ffi::LUA_GCGEN)
};
#[cfg(not(feature = "lua55"))]
let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) }; let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) };
match prev_mode { match prev_mode {
ffi::LUA_GCGEN => GCMode::Generational, ffi::LUA_GCGEN => GCMode::Generational,
@@ -1112,10 +1204,10 @@ impl Lua {
#[doc(hidden)] #[doc(hidden)]
#[allow(clippy::result_unit_err)] #[allow(clippy::result_unit_err)]
pub fn set_fflag(name: &str, enabled: bool) -> StdResult<(), ()> { pub fn set_fflag(name: &str, enabled: bool) -> StdResult<(), ()> {
if let Ok(name) = std::ffi::CString::new(name) { if let Ok(name) = std::ffi::CString::new(name)
if unsafe { ffi::luau_setfflag(name.as_ptr(), enabled as c_int) != 0 } { && unsafe { ffi::luau_setfflag(name.as_ptr(), enabled as c_int) != 0 }
return Ok(()); {
} return Ok(());
} }
Err(()) Err(())
} }
@@ -1150,36 +1242,52 @@ impl Lua {
} }
} }
/// Create and return an interned Lua string. /// Creates and returns an interned Lua string.
/// ///
/// Lua strings can be arbitrary `[u8]` data including embedded nulls, so in addition to `&str` /// Lua strings can be arbitrary `[u8]` data including embedded nulls, so in addition to `&str`
/// and `&String`, you can also pass plain `&[u8]` here. /// and `&String`, you can also pass plain `&[u8]` here.
#[inline] #[inline]
pub fn create_string(&self, s: impl AsRef<[u8]>) -> Result<String> { pub fn create_string(&self, s: impl AsRef<[u8]>) -> Result<LuaString> {
unsafe { self.lock().create_string(s) } unsafe { self.lock().create_string(s.as_ref()) }
} }
/// Create and return a Luau [buffer] object from a byte slice of data. /// Creates and returns an external Lua string.
///
/// External string is a string where the memory is managed by Rust code, and Lua only holds a
/// reference to it. This can be used to avoid copying large strings into Lua memory.
#[cfg(feature = "lua55")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua55")))]
#[inline]
pub fn create_external_string(&self, s: impl Into<Vec<u8>>) -> Result<LuaString> {
unsafe { self.lock().create_external_string(s.into()) }
}
/// Creates and returns a Luau [buffer] object from a byte slice of data.
/// ///
/// [buffer]: https://luau.org/library#buffer-library /// [buffer]: https://luau.org/library#buffer-library
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn create_buffer(&self, buf: impl AsRef<[u8]>) -> Result<Buffer> { pub fn create_buffer(&self, data: impl AsRef<[u8]>) -> Result<Buffer> {
let lua = self.lock(); let lua = self.lock();
let state = lua.state(); let data = data.as_ref();
unsafe { unsafe {
if lua.unlikely_memory_error() { let (ptr, buffer) = lua.create_buffer_with_capacity(data.len())?;
crate::util::push_buffer(state, buf.as_ref(), false)?; ptr.copy_from_nonoverlapping(data.as_ptr(), data.len());
return Ok(Buffer(lua.pop_ref())); Ok(buffer)
}
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
crate::util::push_buffer(state, buf.as_ref(), true)?;
Ok(Buffer(lua.pop_ref()))
} }
} }
/// Creates and returns a Luau [buffer] object with the specified size.
///
/// Size limit is 1GB. All bytes will be initialized to zero.
///
/// [buffer]: https://luau.org/library#buffer-library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn create_buffer_with_capacity(&self, size: usize) -> Result<Buffer> {
unsafe { Ok(self.lock().create_buffer_with_capacity(size)?.1) }
}
/// Creates and returns a new empty table. /// Creates and returns a new empty table.
#[inline] #[inline]
pub fn create_table(&self) -> Result<Table> { pub fn create_table(&self) -> Result<Table> {
@@ -1202,28 +1310,7 @@ impl Lua {
K: IntoLua, K: IntoLua,
V: IntoLua, V: IntoLua,
{ {
let lua = self.lock(); unsafe { self.lock().create_table_from(iter) }
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 6)?;
let iter = iter.into_iter();
let lower_bound = iter.size_hint().0;
let protect = !lua.unlikely_memory_error();
push_table(state, 0, lower_bound, protect)?;
for (k, v) in iter {
lua.push(k)?;
lua.push(v)?;
if protect {
protect_lua!(state, 3, 1, fn(state) ffi::lua_rawset(state, -3))?;
} else {
ffi::lua_rawset(state, -3);
}
}
Ok(Table(lua.pop_ref()))
}
} }
/// Creates a table from an iterator of values, using `1..` as the keys. /// Creates a table from an iterator of values, using `1..` as the keys.
@@ -1309,8 +1396,29 @@ impl Lua {
/// This function is unsafe because provides a way to execute unsafe C function. /// This function is unsafe because provides a way to execute unsafe C function.
pub unsafe fn create_c_function(&self, func: ffi::lua_CFunction) -> Result<Function> { pub unsafe fn create_c_function(&self, func: ffi::lua_CFunction) -> Result<Function> {
let lua = self.lock(); let lua = self.lock();
ffi::lua_pushcfunction(lua.ref_thread(), func); if cfg!(any(
Ok(Function(lua.pop_ref_thread())) feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52"
)) {
ffi::lua_pushcfunction(lua.ref_thread(), func);
return Ok(Function(lua.pop_ref_thread()));
}
// Lua <5.2 requires memory allocation to push a C function
let state = lua.state();
{
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
if lua.unlikely_memory_error() {
ffi::lua_pushcfunction(state, func);
} else {
protect_lua!(state, 0, 1, |state| ffi::lua_pushcfunction(state, func))?;
}
Ok(Function(lua.pop_ref()))
}
} }
/// Wraps a Rust async function or closure, creating a callable Lua function handle to it. /// Wraps a Rust async function or closure, creating a callable Lua function handle to it.
@@ -1390,8 +1498,8 @@ impl Lua {
} }
/// Creates a Lua userdata object from a custom serializable userdata type. /// Creates a Lua userdata object from a custom serializable userdata type.
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
#[inline] #[inline]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData> pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData>
where where
@@ -1418,8 +1526,8 @@ impl Lua {
/// Creates a Lua userdata object from a custom serializable Rust type. /// Creates a Lua userdata object from a custom serializable Rust type.
/// ///
/// See [`Lua::create_any_userdata`] for more details. /// See [`Lua::create_any_userdata`] for more details.
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
#[inline] #[inline]
pub fn create_ser_any_userdata<T>(&self, data: T) -> Result<AnyUserData> pub fn create_ser_any_userdata<T>(&self, data: T) -> Result<AnyUserData>
where where
@@ -1439,7 +1547,7 @@ impl Lua {
let lua = self.lock(); let lua = self.lock();
unsafe { unsafe {
// Deregister the type if it already registered // Deregister the type if it already registered
if let Some(&table_id) = (*lua.extra.get()).registered_userdata_t.get(&type_id) { if let Some(table_id) = (*lua.extra.get()).registered_userdata_t.remove(&type_id) {
ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, table_id); ffi::luaL_unref(lua.state(), ffi::LUA_REGISTRYINDEX, table_id);
} }
@@ -1490,7 +1598,27 @@ impl Lua {
unsafe { self.lock().make_userdata(UserDataStorage::new(ud)) } unsafe { self.lock().make_userdata(UserDataStorage::new(ud)) }
} }
/// Sets the metatable for a Lua builtin type. /// Gets the metatable of a Lua built-in (primitive) type.
///
/// The metatable is shared by all values of the given type.
///
/// See [`Lua::set_type_metatable`] for examples.
#[allow(private_bounds)]
pub fn type_metatable<T: LuaType>(&self) -> Option<Table> {
let lua = self.lock();
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 2);
if lua.push_primitive_type::<T>() && ffi::lua_getmetatable(state, -1) != 0 {
return Some(Table(lua.pop_ref()));
}
}
None
}
/// Sets the metatable for a Lua built-in (primitive) type.
/// ///
/// The metatable will be shared by all values of the given type. /// The metatable will be shared by all values of the given type.
/// ///
@@ -1517,44 +1645,13 @@ impl Lua {
let _sg = StackGuard::new(state); let _sg = StackGuard::new(state);
assert_stack(state, 2); assert_stack(state, 2);
match T::TYPE_ID { if lua.push_primitive_type::<T>() {
ffi::LUA_TBOOLEAN => { match metatable {
ffi::lua_pushboolean(state, 0); Some(metatable) => lua.push_ref(&metatable.0),
None => ffi::lua_pushnil(state),
} }
ffi::LUA_TLIGHTUSERDATA => { ffi::lua_setmetatable(state, -2);
ffi::lua_pushlightuserdata(state, ptr::null_mut());
}
ffi::LUA_TNUMBER => {
ffi::lua_pushnumber(state, 0.);
}
#[cfg(feature = "luau")]
ffi::LUA_TVECTOR => {
#[cfg(not(feature = "luau-vector4"))]
ffi::lua_pushvector(state, 0., 0., 0.);
#[cfg(feature = "luau-vector4")]
ffi::lua_pushvector(state, 0., 0., 0., 0.);
}
ffi::LUA_TSTRING => {
ffi::lua_pushstring(state, b"\0" as *const u8 as *const _);
}
ffi::LUA_TFUNCTION => match self.load("function() end").eval::<Function>() {
Ok(func) => lua.push_ref(&func.0),
Err(_) => return,
},
ffi::LUA_TTHREAD => {
ffi::lua_pushthread(state);
}
#[cfg(feature = "luau")]
ffi::LUA_TBUFFER => {
ffi::lua_newbuffer(state, 0);
}
_ => return,
} }
match metatable {
Some(metatable) => lua.push_ref(&metatable.0),
None => ffi::lua_pushnil(state),
}
ffi::lua_setmetatable(state, -2);
} }
} }
@@ -1565,7 +1662,7 @@ impl Lua {
unsafe { unsafe {
let _sg = StackGuard::new(state); let _sg = StackGuard::new(state);
assert_stack(state, 1); assert_stack(state, 1);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS);
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX); ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX);
@@ -1573,6 +1670,39 @@ impl Lua {
} }
} }
/// Sets the global environment.
///
/// This will replace the current global environment with the provided `globals` table.
///
/// For Lua 5.2+ the globals table is stored in the registry and shared between all threads.
/// For Lua 5.1 and Luau the globals table is stored in each thread.
///
/// Please note that any existing Lua functions have cached global environment and will not
/// see the changes made by this method.
/// To update the environment for existing Lua functions, use [`Function::set_environment`].
pub fn set_globals(&self, globals: Table) -> Result<()> {
let lua = self.lock();
let state = lua.state();
unsafe {
#[cfg(feature = "luau")]
if (*lua.extra.get()).sandboxed {
return Err(Error::runtime("cannot change globals in a sandboxed Lua state"));
}
let _sg = StackGuard::new(state);
check_stack(state, 1)?;
lua.push_ref(&globals.0);
#[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS);
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::lua_replace(state, ffi::LUA_GLOBALSINDEX);
}
Ok(())
}
/// Returns a handle to the active `Thread`. /// Returns a handle to the active `Thread`.
/// ///
/// For calls to `Lua` this will be the main Lua thread, for parameters given to a callback, /// For calls to `Lua` this will be the main Lua thread, for parameters given to a callback,
@@ -1610,7 +1740,7 @@ impl Lua {
/// ///
/// To succeed, the value must be a string (in which case this is a no-op), an integer, or a /// To succeed, the value must be a string (in which case this is a no-op), an integer, or a
/// number. /// number.
pub fn coerce_string(&self, v: Value) -> Result<Option<String>> { pub fn coerce_string(&self, v: Value) -> Result<Option<LuaString>> {
Ok(match v { Ok(match v {
Value::String(s) => Some(s), Value::String(s) => Some(s),
v => unsafe { v => unsafe {
@@ -1628,7 +1758,7 @@ impl Lua {
})? })?
}; };
if !res.is_null() { if !res.is_null() {
Some(String(lua.pop_ref())) Some(LuaString(lua.pop_ref()))
} else { } else {
None None
} }
@@ -1654,11 +1784,7 @@ impl Lua {
lua.push_value(&v)?; lua.push_value(&v)?;
let mut isint = 0; let mut isint = 0;
let i = ffi::lua_tointegerx(state, -1, &mut isint); let i = ffi::lua_tointegerx(state, -1, &mut isint);
if isint == 0 { if isint == 0 { None } else { Some(i) }
None
} else {
Some(i)
}
}, },
}) })
} }
@@ -1680,11 +1806,7 @@ impl Lua {
lua.push_value(&v)?; lua.push_value(&v)?;
let mut isnum = 0; let mut isnum = 0;
let n = ffi::lua_tonumberx(state, -1, &mut isnum); let n = ffi::lua_tonumberx(state, -1, &mut isnum);
if isnum == 0 { if isnum == 0 { None } else { Some(n) }
None
} else {
Some(n)
}
}, },
}) })
} }
@@ -1978,7 +2100,7 @@ impl Lua {
/// Panics if the data object of type `T` is currently mutably borrowed. Multiple immutable /// Panics if the data object of type `T` is currently mutably borrowed. Multiple immutable
/// reads can be taken out at the same time. /// reads can be taken out at the same time.
#[track_caller] #[track_caller]
pub fn app_data_ref<T: 'static>(&self) -> Option<AppDataRef<T>> { pub fn app_data_ref<T: 'static>(&self) -> Option<AppDataRef<'_, T>> {
let guard = self.lock_arc(); let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() }; let extra = unsafe { &*guard.extra.get() };
extra.app_data.borrow(Some(guard)) extra.app_data.borrow(Some(guard))
@@ -1986,7 +2108,7 @@ impl Lua {
/// Tries to get a reference to an application data object stored by [`Lua::set_app_data`] of /// Tries to get a reference to an application data object stored by [`Lua::set_app_data`] of
/// type `T`. /// type `T`.
pub fn try_app_data_ref<T: 'static>(&self) -> StdResult<Option<AppDataRef<T>>, BorrowError> { pub fn try_app_data_ref<T: 'static>(&self) -> StdResult<Option<AppDataRef<'_, T>>, BorrowError> {
let guard = self.lock_arc(); let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() }; let extra = unsafe { &*guard.extra.get() };
extra.app_data.try_borrow(Some(guard)) extra.app_data.try_borrow(Some(guard))
@@ -1999,7 +2121,7 @@ impl Lua {
/// ///
/// Panics if the data object of type `T` is currently borrowed. /// Panics if the data object of type `T` is currently borrowed.
#[track_caller] #[track_caller]
pub fn app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<T>> { pub fn app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<'_, T>> {
let guard = self.lock_arc(); let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() }; let extra = unsafe { &*guard.extra.get() };
extra.app_data.borrow_mut(Some(guard)) extra.app_data.borrow_mut(Some(guard))
@@ -2007,7 +2129,7 @@ impl Lua {
/// Tries to get a mutable reference to an application data object stored by /// Tries to get a mutable reference to an application data object stored by
/// [`Lua::set_app_data`] of type `T`. /// [`Lua::set_app_data`] of type `T`.
pub fn try_app_data_mut<T: 'static>(&self) -> StdResult<Option<AppDataRefMut<T>>, BorrowMutError> { pub fn try_app_data_mut<T: 'static>(&self) -> StdResult<Option<AppDataRefMut<'_, T>>, BorrowMutError> {
let guard = self.lock_arc(); let guard = self.lock_arc();
let extra = unsafe { &*guard.extra.get() }; let extra = unsafe { &*guard.extra.get() };
extra.app_data.try_borrow_mut(Some(guard)) extra.app_data.try_borrow_mut(Some(guard))
@@ -2036,6 +2158,112 @@ impl Lua {
LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut std::os::raw::c_void) LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut std::os::raw::c_void)
} }
#[cfg(feature = "async")]
#[inline(always)]
pub(crate) fn poll_terminate() -> LightUserData {
static ASYNC_POLL_TERMINATE: u8 = 0;
LightUserData(&ASYNC_POLL_TERMINATE as *const u8 as *mut std::os::raw::c_void)
}
#[cfg(feature = "async")]
#[inline(always)]
pub(crate) fn poll_yield() -> LightUserData {
static ASYNC_POLL_YIELD: u8 = 0;
LightUserData(&ASYNC_POLL_YIELD as *const u8 as *mut std::os::raw::c_void)
}
/// Suspends the current async function, returning the provided arguments to caller.
///
/// This function is similar to [`coroutine.yield`] but allow yielding Rust functions
/// and passing values to the caller.
/// Please note that you cannot cross [`Thread`] boundaries (e.g. calling `yield_with` on one
/// thread and resuming on another).
///
/// # Examples
///
/// Async iterator:
///
/// ```
/// # use mlua::{Lua, Result};
/// #
/// async fn generator(lua: Lua, _: ()) -> Result<()> {
/// for i in 0..10 {
/// lua.yield_with::<()>(i).await?;
/// }
/// Ok(())
/// }
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// lua.globals().set("generator", lua.create_async_function(generator)?)?;
///
/// lua.load(r#"
/// local n = 0
/// for i in coroutine.wrap(generator) do
/// n = n + i
/// end
/// assert(n == 45)
/// "#)
/// .exec()
/// }
/// ```
///
/// Exchange values on yield:
///
/// ```
/// # use mlua::{Lua, Result, Value};
/// #
/// async fn pingpong(lua: Lua, mut val: i32) -> Result<()> {
/// loop {
/// val = lua.yield_with::<i32>(val).await? + 1;
/// }
/// Ok(())
/// }
///
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
///
/// let co = lua.create_thread(lua.create_async_function(pingpong)?)?;
/// assert_eq!(co.resume::<i32>(1)?, 1);
/// assert_eq!(co.resume::<i32>(2)?, 3);
/// assert_eq!(co.resume::<i32>(3)?, 4);
///
/// # Ok(())
/// # }
/// ```
///
/// [`coroutine.yield`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.yield
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn yield_with<R: FromLuaMulti>(&self, args: impl IntoLuaMulti) -> Result<R> {
let mut args = Some(args.into_lua_multi(self)?);
future::poll_fn(move |_cx| match args.take() {
Some(args) => unsafe {
let lua = self.lock();
lua.push(Self::poll_yield())?; // yield marker
if args.len() <= 1 {
lua.push(args.front())?;
} else {
lua.push(lua.create_sequence_from(&args)?)?;
}
lua.push(args.len())?;
Poll::Pending
},
None => unsafe {
let lua = self.lock();
let state = lua.state();
let top = ffi::lua_gettop(state);
if top == 0 || ffi::lua_type(state, 1) != ffi::LUA_TUSERDATA {
// This must be impossible scenario if used correctly
return Poll::Ready(R::from_stack_multi(0, &lua));
}
let _sg = StackGuard::with_top(state, 1);
Poll::Ready(R::from_stack_multi(top - 1, &lua))
},
})
.await
}
/// Returns a weak reference to the Lua instance. /// Returns a weak reference to the Lua instance.
/// ///
/// This is useful for creating a reference to the Lua instance that does not prevent it from /// This is useful for creating a reference to the Lua instance that does not prevent it from
@@ -2045,7 +2273,6 @@ impl Lua {
WeakLua(XRc::downgrade(&self.raw)) WeakLua(XRc::downgrade(&self.raw))
} }
// Luau version located in `luau/mod.rs`
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
fn disable_c_modules(&self) -> Result<()> { fn disable_c_modules(&self) -> Result<()> {
let package: Table = self.globals().get("package")?; let package: Table = self.globals().get("package")?;
@@ -2059,7 +2286,7 @@ impl Lua {
})?, })?,
)?; )?;
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
let searchers: Table = package.get("searchers")?; let searchers: Table = package.get("searchers")?;
#[cfg(any(feature = "lua51", feature = "luajit"))] #[cfg(any(feature = "lua51", feature = "luajit"))]
let searchers: Table = package.get("loaders")?; let searchers: Table = package.get("loaders")?;
@@ -2068,13 +2295,15 @@ impl Lua {
// The third and fourth searchers looks for a loader as a C library // The third and fourth searchers looks for a loader as a C library
searchers.raw_set(3, loader)?; searchers.raw_set(3, loader)?;
searchers.raw_remove(4)?; if searchers.raw_len() >= 4 {
searchers.raw_remove(4)?;
}
Ok(()) Ok(())
} }
#[inline(always)] #[inline(always)]
pub(crate) fn lock(&self) -> ReentrantMutexGuard<RawLua> { pub(crate) fn lock(&self) -> ReentrantMutexGuard<'_, RawLua> {
let rawlua = self.raw.lock(); let rawlua = self.raw.lock();
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
if unsafe { (*rawlua.extra.get()).running_gc } { if unsafe { (*rawlua.extra.get()).running_gc } {
+43 -9
View File
@@ -14,7 +14,7 @@ use crate::state::RawLua;
use crate::stdlib::StdLib; use crate::stdlib::StdLib;
use crate::types::{AppData, ReentrantMutex, XRc}; use crate::types::{AppData, ReentrantMutex, XRc};
use crate::userdata::RawUserDataRegistry; use crate::userdata::RawUserDataRegistry;
use crate::util::{get_internal_metatable, push_internal_userdata, TypeKey, WrappedFailure}; use crate::util::{TypeKey, WrappedFailure, get_internal_metatable, push_internal_userdata};
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
use crate::chunk::Compiler; use crate::chunk::Compiler;
@@ -28,7 +28,7 @@ use super::{Lua, WeakLua};
static EXTRA_REGISTRY_KEY: u8 = 0; static EXTRA_REGISTRY_KEY: u8 = 0;
const WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY: usize = 64; const WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY: usize = 64;
const REF_STACK_RESERVE: c_int = 2; const REF_STACK_RESERVE: c_int = 3;
/// Data associated with the Lua state. /// Data associated with the Lua state.
pub(crate) struct ExtraData { pub(crate) struct ExtraData {
@@ -44,8 +44,9 @@ pub(crate) struct ExtraData {
// When Lua instance dropped, setting `None` would prevent collecting `RegistryKey`s // When Lua instance dropped, setting `None` would prevent collecting `RegistryKey`s
pub(super) registry_unref_list: Arc<Mutex<Option<Vec<c_int>>>>, pub(super) registry_unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
// Container to store arbitrary data (extensions) // Containers to store arbitrary data (extensions)
pub(super) app_data: AppData, pub(super) app_data: AppData,
pub(super) app_data_priv: AppData,
pub(super) safe: bool, pub(super) safe: bool,
pub(super) libs: StdLib, pub(super) libs: StdLib,
@@ -63,7 +64,7 @@ pub(crate) struct ExtraData {
pub(super) wrapped_failure_top: usize, pub(super) wrapped_failure_top: usize,
// Pool of `Thread`s (coroutines) for async execution // Pool of `Thread`s (coroutines) for async execution
#[cfg(feature = "async")] #[cfg(feature = "async")]
pub(super) thread_pool: Vec<c_int>, pub(super) thread_pool: Vec<crate::types::ValueRefIndex>,
// Address of `WrappedFailure` metatable // Address of `WrappedFailure` metatable
pub(super) wrapped_failure_mt_ptr: *const c_void, pub(super) wrapped_failure_mt_ptr: *const c_void,
@@ -75,8 +76,8 @@ pub(crate) struct ExtraData {
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
pub(super) hook_callback: Option<crate::types::HookCallback>, pub(super) hook_callback: Option<crate::types::HookCallback>,
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
pub(super) hook_triggers: crate::hook::HookTriggers, pub(super) hook_triggers: crate::debug::HookTriggers,
#[cfg(feature = "lua54")] #[cfg(any(feature = "lua55", feature = "lua54"))]
pub(super) warn_callback: Option<crate::types::WarnCallback>, pub(super) warn_callback: Option<crate::types::WarnCallback>,
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
pub(super) interrupt_callback: Option<crate::types::InterruptCallback>, pub(super) interrupt_callback: Option<crate::types::InterruptCallback>,
@@ -88,11 +89,13 @@ pub(crate) struct ExtraData {
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
pub(crate) running_gc: bool, pub(crate) running_gc: bool,
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
pub(super) sandboxed: bool, pub(crate) sandboxed: bool,
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
pub(super) compiler: Option<Compiler>, pub(super) compiler: Option<Compiler>,
#[cfg(feature = "luau-jit")] #[cfg(feature = "luau-jit")]
pub(super) enable_jit: bool, pub(super) enable_jit: bool,
#[cfg(feature = "luau")]
pub(crate) mem_categories: Vec<std::ffi::CString>,
} }
impl Drop for ExtraData { impl Drop for ExtraData {
@@ -159,6 +162,7 @@ impl ExtraData {
last_checked_userdata_mt: (ptr::null(), None), last_checked_userdata_mt: (ptr::null(), None),
registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))), registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))),
app_data: AppData::default(), app_data: AppData::default(),
app_data_priv: AppData::default(),
safe: false, safe: false,
libs: StdLib::NONE, libs: StdLib::NONE,
skip_memory_check: false, skip_memory_check: false,
@@ -178,7 +182,7 @@ impl ExtraData {
hook_callback: None, hook_callback: None,
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
hook_triggers: Default::default(), hook_triggers: Default::default(),
#[cfg(feature = "lua54")] #[cfg(any(feature = "lua55", feature = "lua54"))]
warn_callback: None, warn_callback: None,
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
interrupt_callback: None, interrupt_callback: None,
@@ -194,6 +198,8 @@ impl ExtraData {
enable_jit: true, enable_jit: true,
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
running_gc: false, running_gc: false,
#[cfg(feature = "luau")]
mem_categories: vec![std::ffi::CString::new("main").unwrap()],
})); }));
// Store it in the registry // Store it in the registry
@@ -210,7 +216,7 @@ impl ExtraData {
self.weak.write(WeakLua(XRc::downgrade(raw))); self.weak.write(WeakLua(XRc::downgrade(raw)));
} }
pub(super) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self { pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
if cfg!(not(feature = "module")) { if cfg!(not(feature = "module")) {
// In the main app we can use `lua_callbacks` to access ExtraData // In the main app we can use `lua_callbacks` to access ExtraData
@@ -257,4 +263,32 @@ impl ExtraData {
pub(super) unsafe fn weak(&self) -> &WeakLua { pub(super) unsafe fn weak(&self) -> &WeakLua {
self.weak.assume_init_ref() self.weak.assume_init_ref()
} }
/// Pops a reference from top of the auxiliary stack and move it to a first free slot.
pub(super) unsafe fn ref_stack_pop(&mut self) -> c_int {
if let Some(free) = self.ref_free.pop() {
ffi::lua_replace(self.ref_thread, free);
return free;
}
// Try to grow max stack size
if self.ref_stack_top >= self.ref_stack_size {
let mut inc = self.ref_stack_size; // Try to double stack size
while inc > 0 && ffi::lua_checkstack(self.ref_thread, inc + REF_STACK_RESERVE) == 0 {
inc /= 2;
}
if inc == 0 {
// Pop item on top of the stack to avoid stack leaking and successfully run destructors
// during unwinding.
ffi::lua_pop(self.ref_thread, 1);
let top = self.ref_stack_top;
// It is a user error to create too many references to exhaust the Lua max stack size
// for the ref thread.
panic!("cannot create a Lua reference, out of auxiliary stack space (used {top} slots)");
}
self.ref_stack_size += inc;
}
self.ref_stack_top += 1;
self.ref_stack_top
}
} }
+243 -124
View File
@@ -5,32 +5,31 @@ use std::mem;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::panic::resume_unwind; use std::panic::resume_unwind;
use std::ptr::{self, NonNull}; use std::ptr::{self, NonNull};
use std::result::Result as StdResult;
use std::sync::Arc; use std::sync::Arc;
use crate::chunk::ChunkMode; use crate::chunk::ChunkMode;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::function::Function; use crate::function::Function;
use crate::memory::{MemoryState, ALLOCATOR}; use crate::memory::{ALLOCATOR, MemoryState};
use crate::state::util::{callback_error_ext, ref_stack_pop}; use crate::state::util::callback_error_ext;
use crate::stdlib::StdLib; use crate::stdlib::StdLib;
use crate::string::String; use crate::string::LuaString;
use crate::table::Table; use crate::table::Table;
use crate::thread::Thread; use crate::thread::Thread;
use crate::traits::IntoLua; use crate::traits::IntoLua;
use crate::types::{ use crate::types::{
AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData, AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData,
MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc, LuaType, MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc,
}; };
use crate::userdata::{ use crate::userdata::{
init_userdata_metatable, AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, AnyUserData, MetaMethod, RawUserDataRegistry, UserData, UserDataRegistry, UserDataStorage,
UserDataStorage, init_userdata_metatable,
}; };
use crate::util::{ use crate::util::{
assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state, StackGuard, WrappedFailure, assert_stack, check_stack, get_destructed_userdata_metatable,
get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, pop_error, get_internal_userdata, get_main_state, get_metatable_ptr, get_userdata, init_error_registry,
push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, init_internal_metatable, pop_error, push_internal_userdata, push_string, push_table, push_userdata,
StackGuard, WrappedFailure, rawset_field, safe_pcall, safe_xpcall, short_type_name,
}; };
use crate::value::{Nil, Value}; use crate::value::{Nil, Value};
@@ -39,7 +38,7 @@ use super::{Lua, LuaOptions, WeakLua};
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
use crate::{ use crate::{
hook::Debug, debug::Debug,
types::{HookCallback, HookKind, VmState}, types::{HookCallback, HookKind, VmState},
}; };
@@ -123,6 +122,12 @@ impl RawLua {
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 mem_state: *mut MemoryState = Box::into_raw(Box::default());
#[cfg(feature = "lua55")]
let mut state = {
let seed = ffi::luaL_makeseed(ptr::null_mut());
ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void, seed)
};
#[cfg(not(feature = "lua55"))]
let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void); let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void);
// If state is null then switch to Lua internal allocator // If state is null then switch to Lua internal allocator
if state.is_null() { if state.is_null() {
@@ -154,7 +159,7 @@ impl RawLua {
(|| -> Result<()> { (|| -> Result<()> {
let _sg = StackGuard::new(state); let _sg = StackGuard::new(state);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS);
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX); ffi::lua_pushvalue(state, ffi::LUA_GLOBALSINDEX);
@@ -209,7 +214,7 @@ impl RawLua {
} }
// Init serde metatables // Init serde metatables
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
crate::serde::init_metatables(state)?; crate::serde::init_metatables(state)?;
Ok::<_, Error>(()) Ok::<_, Error>(())
@@ -297,7 +302,7 @@ impl RawLua {
if is_safe { if is_safe {
let curr_libs = (*self.extra.get()).libs; let curr_libs = (*self.extra.get()).libs;
if (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) { if (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) {
mlua_expect!(self.lua().disable_c_modules(), "Error during disabling C modules"); mlua_expect!(self.lua().disable_c_modules(), "Error disabling C modules");
} }
} }
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
@@ -307,27 +312,27 @@ impl RawLua {
res res
} }
/// See [`Lua::try_set_app_data`] /// Private version of [`Lua::try_set_app_data`]
#[inline] #[inline]
pub(crate) fn try_set_app_data<T: MaybeSend + 'static>(&self, data: T) -> StdResult<Option<T>, T> { pub(crate) fn set_priv_app_data<T: MaybeSend + 'static>(&self, data: T) -> Option<T> {
let extra = unsafe { &*self.extra.get() }; let extra = unsafe { &*self.extra.get() };
extra.app_data.try_insert(data) extra.app_data_priv.insert(data)
} }
/// See [`Lua::app_data_ref`] /// Private version of [`Lua::app_data_ref`]
#[track_caller] #[track_caller]
#[inline] #[inline]
pub(crate) fn app_data_ref_unguarded<T: 'static>(&self) -> Option<AppDataRef<T>> { pub(crate) fn priv_app_data_ref<T: 'static>(&self) -> Option<AppDataRef<'_, T>> {
let extra = unsafe { &*self.extra.get() }; let extra = unsafe { &*self.extra.get() };
extra.app_data.borrow(None) extra.app_data_priv.borrow(None)
} }
/// See [`Lua::app_data_mut`] /// Private version of [`Lua::app_data_mut`]
#[track_caller] #[track_caller]
#[inline] #[inline]
pub(crate) fn app_data_mut_unguarded<T: 'static>(&self) -> Option<AppDataRefMut<T>> { pub(crate) fn priv_app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<'_, T>> {
let extra = unsafe { &*self.extra.get() }; let extra = unsafe { &*self.extra.get() };
extra.app_data.borrow_mut(None) extra.app_data_priv.borrow_mut(None)
} }
/// See [`Lua::create_registry_value`] /// See [`Lua::create_registry_value`]
@@ -417,7 +422,7 @@ impl RawLua {
VmState::Yield => { VmState::Yield => {
// Only count and line events can yield // Only count and line events can yield
if event == ffi::LUA_HOOKCOUNT || event == ffi::LUA_HOOKLINE { if event == ffi::LUA_HOOKCOUNT || event == ffi::LUA_HOOKLINE {
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
if ffi::lua_isyieldable(state) != 0 { if ffi::lua_isyieldable(state) != 0 {
ffi::lua_yield(state, 0); ffi::lua_yield(state, 0);
} }
@@ -436,8 +441,8 @@ impl RawLua {
match (*extra).hook_callback.clone() { match (*extra).hook_callback.clone() {
Some(hook_callback) => { Some(hook_callback) => {
let rawlua = (*extra).raw_lua(); let rawlua = (*extra).raw_lua();
let debug = Debug::new(rawlua, ar); let debug = Debug::new(rawlua, 0, ar);
hook_callback((*extra).lua(), debug) hook_callback((*extra).lua(), &debug)
} }
None => { None => {
ffi::lua_sethook(state, None, 0, 0); ffi::lua_sethook(state, None, 0, 0);
@@ -466,9 +471,9 @@ impl RawLua {
let status = callback_error_ext(state, ptr::null_mut(), false, |extra, _| { let status = callback_error_ext(state, ptr::null_mut(), false, |extra, _| {
let rawlua = (*extra).raw_lua(); let rawlua = (*extra).raw_lua();
let debug = Debug::new(rawlua, ar); let debug = Debug::new(rawlua, 0, ar);
let hook_callback = (*hook_callback_ptr).clone(); let hook_callback = (*hook_callback_ptr).clone();
hook_callback((*extra).lua(), debug) hook_callback((*extra).lua(), &debug)
}); });
process_status(state, (*ar).event, status) process_status(state, (*ar).event, status)
} }
@@ -511,17 +516,48 @@ impl RawLua {
} }
/// See [`Lua::create_string`] /// See [`Lua::create_string`]
pub(crate) unsafe fn create_string(&self, s: impl AsRef<[u8]>) -> Result<String> { pub(crate) unsafe fn create_string(&self, s: &[u8]) -> Result<LuaString> {
let state = self.state(); let state = self.state();
if self.unlikely_memory_error() { if self.unlikely_memory_error() {
push_string(state, s.as_ref(), false)?; push_string(state, s, false)?;
return Ok(String(self.pop_ref())); return Ok(LuaString(self.pop_ref()));
} }
let _sg = StackGuard::new(state); let _sg = StackGuard::new(state);
check_stack(state, 3)?; check_stack(state, 3)?;
push_string(state, s.as_ref(), true)?; push_string(state, s, true)?;
Ok(String(self.pop_ref())) Ok(LuaString(self.pop_ref()))
}
/// Creates an external string, that is, a string that uses memory not managed by Lua.
///
/// Modifies the input data to add `\0` terminator.
#[cfg(feature = "lua55")]
pub(crate) unsafe fn create_external_string(&self, bytes: Vec<u8>) -> Result<LuaString> {
let state = self.state();
if self.unlikely_memory_error() {
crate::util::push_external_string(state, bytes, false)?;
return Ok(LuaString(self.pop_ref()));
}
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
crate::util::push_external_string(state, bytes, true)?;
Ok(LuaString(self.pop_ref()))
}
#[cfg(feature = "luau")]
pub(crate) unsafe fn create_buffer_with_capacity(&self, size: usize) -> Result<(*mut u8, crate::Buffer)> {
let state = self.state();
if self.unlikely_memory_error() {
let ptr = crate::util::push_buffer(state, size, false)?;
return Ok((ptr, crate::Buffer(self.pop_ref())));
}
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
let ptr = crate::util::push_buffer(state, size, true)?;
Ok((ptr, crate::Buffer(self.pop_ref())))
} }
/// See [`Lua::create_table_with_capacity`] /// See [`Lua::create_table_with_capacity`]
@@ -538,6 +574,34 @@ impl RawLua {
Ok(Table(self.pop_ref())) Ok(Table(self.pop_ref()))
} }
/// See [`Lua::create_table_from`]
pub(crate) unsafe fn create_table_from<I, K, V>(&self, iter: I) -> Result<Table>
where
I: IntoIterator<Item = (K, V)>,
K: IntoLua,
V: IntoLua,
{
let state = self.state();
let _sg = StackGuard::new(state);
check_stack(state, 6)?;
let iter = iter.into_iter();
let lower_bound = iter.size_hint().0;
let protect = !self.unlikely_memory_error();
push_table(state, 0, lower_bound, protect)?;
for (k, v) in iter {
self.push(k)?;
self.push(v)?;
if protect {
protect_lua!(state, 3, 1, fn(state) ffi::lua_rawset(state, -3))?;
} else {
ffi::lua_rawset(state, -3);
}
}
Ok(Table(self.pop_ref()))
}
/// See [`Lua::create_sequence_from`] /// See [`Lua::create_sequence_from`]
pub(crate) unsafe fn create_sequence_from<T, I>(&self, iter: I) -> Result<Table> pub(crate) unsafe fn create_sequence_from<T, I>(&self, iter: I) -> Result<Table>
where where
@@ -597,7 +661,7 @@ impl RawLua {
#[cfg(feature = "async")] #[cfg(feature = "async")]
pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result<Thread> { pub(crate) unsafe fn create_recycled_thread(&self, func: &Function) -> Result<Thread> {
if let Some(index) = (*self.extra.get()).thread_pool.pop() { if let Some(index) = (*self.extra.get()).thread_pool.pop() {
let thread_state = ffi::lua_tothread(self.ref_thread(), index); let thread_state = ffi::lua_tothread(self.ref_thread(), *index.0);
ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index); ffi::lua_xpush(self.ref_thread(), thread_state, func.0.index);
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
@@ -613,63 +677,69 @@ impl RawLua {
self.create_thread(func) self.create_thread(func)
} }
/// Resets thread (coroutine) and returns it to the pool for later use. /// Returns the thread to the pool for later use.
#[cfg(feature = "async")] #[cfg(feature = "async")]
pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) { pub(crate) unsafe fn recycle_thread(&self, thread: &mut Thread) {
let thread_state = thread.1;
let extra = &mut *self.extra.get(); let extra = &mut *self.extra.get();
if extra.thread_pool.len() == extra.thread_pool.capacity() { if extra.thread_pool.len() < extra.thread_pool.capacity()
#[cfg(feature = "lua54")] && let Some(index) = thread.0.index_count.take()
if ffi::lua_status(thread_state) != ffi::LUA_OK { {
// Close all to-be-closed variables without returning thread to the pool extra.thread_pool.push(index);
#[cfg(not(feature = "vendored"))] }
ffi::lua_resetthread(thread_state); }
#[cfg(feature = "vendored")]
ffi::lua_closethread(thread_state, self.state()); /// Pushes a primitive type value onto the Lua stack.
pub(crate) unsafe fn push_primitive_type<T: LuaType>(&self) -> bool {
match T::TYPE_ID {
ffi::LUA_TBOOLEAN => {
ffi::lua_pushboolean(self.state(), 0);
} }
return; ffi::LUA_TLIGHTUSERDATA => {
} ffi::lua_pushlightuserdata(self.state(), ptr::null_mut());
let mut reset_ok = false;
if ffi::lua_status(thread_state) == ffi::LUA_OK {
if ffi::lua_gettop(thread_state) > 0 {
ffi::lua_settop(thread_state, 0);
} }
reset_ok = true; ffi::LUA_TNUMBER => {
} ffi::lua_pushnumber(self.state(), 0.);
}
#[cfg(feature = "lua54")] #[cfg(feature = "luau")]
if !reset_ok { ffi::LUA_TVECTOR => {
#[cfg(not(feature = "vendored"))] #[cfg(not(feature = "luau-vector4"))]
let status = ffi::lua_resetthread(thread_state); ffi::lua_pushvector(self.state(), 0., 0., 0.);
#[cfg(feature = "vendored")] #[cfg(feature = "luau-vector4")]
let status = ffi::lua_closethread(thread_state, self.state()); ffi::lua_pushvector(self.state(), 0., 0., 0., 0.);
reset_ok = status == ffi::LUA_OK; }
} ffi::LUA_TSTRING => {
#[cfg(feature = "luau")] ffi::lua_pushstring(self.state(), b"\0" as *const u8 as *const _);
if !reset_ok { }
ffi::lua_resetthread(thread_state); ffi::LUA_TFUNCTION => {
reset_ok = true; unsafe extern "C-unwind" fn func(_state: *mut ffi::lua_State) -> c_int {
} 0
}
if reset_ok { ffi::lua_pushcfunction(self.state(), func);
extra.thread_pool.push(thread.0.index); }
thread.0.drop = false; // Prevent thread from being garbage collected ffi::LUA_TTHREAD => {
ffi::lua_pushthread(self.state());
}
#[cfg(feature = "luau")]
ffi::LUA_TBUFFER => {
ffi::lua_newbuffer(self.state(), 0);
}
_ => return false,
} }
true
} }
/// Pushes a value that implements `IntoLua` onto the Lua stack. /// Pushes a value that implements `IntoLua` onto the Lua stack.
/// ///
/// Uses up to 2 stack spaces to push a single value, does not call `checkstack`. /// Uses up to 2 stack spaces to push a single value, does not call `checkstack`.
#[inline(always)] #[inline(always)]
pub(crate) unsafe fn push(&self, value: impl IntoLua) -> Result<()> { pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> {
value.push_into_stack(self) value.push_into_stack(self)
} }
/// Pushes a `Value` (by reference) onto the Lua stack. /// Pushes a `Value` (by reference) onto the Lua stack.
/// ///
/// Uses 2 stack spaces, does not call `checkstack`. /// Uses 2 stack spaces, does not call `checkstack`.
pub(crate) unsafe fn push_value(&self, value: &Value) -> Result<()> { pub unsafe fn push_value(&self, value: &Value) -> Result<()> {
let state = self.state(); let state = self.state();
match value { match value {
Value::Nil => ffi::lua_pushnil(state), Value::Nil => ffi::lua_pushnil(state),
@@ -702,8 +772,9 @@ impl RawLua {
/// Pops a value from the Lua stack. /// Pops a value from the Lua stack.
/// ///
/// Uses 2 stack spaces, does not call `checkstack`. /// Uses up to 1 stack spaces, does not call `checkstack`.
pub(crate) unsafe fn pop_value(&self) -> Value { #[inline]
pub unsafe fn pop_value(&self) -> Value {
let value = self.stack_value(-1, None); let value = self.stack_value(-1, None);
ffi::lua_pop(self.state(), 1); ffi::lua_pop(self.state(), 1);
value value
@@ -711,7 +782,7 @@ impl RawLua {
/// Returns value at given stack index without popping it. /// Returns value at given stack index without popping it.
/// ///
/// Uses 2 stack spaces, does not call checkstack. /// Uses up to 1 stack spaces, does not call `checkstack`.
pub(crate) unsafe fn stack_value(&self, idx: c_int, type_hint: Option<c_int>) -> Value { pub(crate) unsafe fn stack_value(&self, idx: c_int, type_hint: Option<c_int>) -> Value {
let state = self.state(); let state = self.state();
match type_hint.unwrap_or_else(|| ffi::lua_type(state, idx)) { match type_hint.unwrap_or_else(|| ffi::lua_type(state, idx)) {
@@ -721,7 +792,7 @@ impl RawLua {
ffi::LUA_TLIGHTUSERDATA => Value::LightUserData(LightUserData(ffi::lua_touserdata(state, idx))), ffi::LUA_TLIGHTUSERDATA => Value::LightUserData(LightUserData(ffi::lua_touserdata(state, idx))),
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
ffi::LUA_TNUMBER => { ffi::LUA_TNUMBER => {
if ffi::lua_isinteger(state, idx) != 0 { if ffi::lua_isinteger(state, idx) != 0 {
Value::Integer(ffi::lua_tointeger(state, idx)) Value::Integer(ffi::lua_tointeger(state, idx))
@@ -736,7 +807,7 @@ impl RawLua {
let n = ffi::lua_tonumber(state, idx); let n = ffi::lua_tonumber(state, idx);
match num_traits::cast(n) { match num_traits::cast(n) {
Some(i) if (n - (i as Number)).abs() < Number::EPSILON => Value::Integer(i), Some(i) if n.to_bits() == (i as Number).to_bits() => Value::Integer(i),
_ => Value::Number(n), _ => Value::Number(n),
} }
} }
@@ -753,7 +824,7 @@ impl RawLua {
ffi::LUA_TSTRING => { ffi::LUA_TSTRING => {
ffi::lua_xpush(state, self.ref_thread(), idx); ffi::lua_xpush(state, self.ref_thread(), idx);
Value::String(String(self.pop_ref_thread())) Value::String(LuaString(self.pop_ref_thread()))
} }
ffi::LUA_TTABLE => { ffi::LUA_TTABLE => {
@@ -824,21 +895,14 @@ impl RawLua {
#[inline] #[inline]
pub(crate) unsafe fn pop_ref(&self) -> ValueRef { pub(crate) unsafe fn pop_ref(&self) -> ValueRef {
ffi::lua_xmove(self.state(), self.ref_thread(), 1); ffi::lua_xmove(self.state(), self.ref_thread(), 1);
let index = ref_stack_pop(self.extra.get()); let index = (*self.extra.get()).ref_stack_pop();
ValueRef::new(self, index) ValueRef::new(self, index)
} }
// Same as `pop_ref` but assumes the value is already on the reference thread // Same as `pop_ref` but assumes the value is already on the reference thread
#[inline] #[inline]
pub(crate) unsafe fn pop_ref_thread(&self) -> ValueRef { pub(crate) unsafe fn pop_ref_thread(&self) -> ValueRef {
let index = ref_stack_pop(self.extra.get()); let index = (*self.extra.get()).ref_stack_pop();
ValueRef::new(self, index)
}
#[inline]
pub(crate) unsafe fn clone_ref(&self, vref: &ValueRef) -> ValueRef {
ffi::lua_pushvalue(self.ref_thread(), vref.index);
let index = ref_stack_pop(self.extra.get());
ValueRef::new(self, index) ValueRef::new(self, index)
} }
@@ -859,7 +923,7 @@ impl RawLua {
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))] #[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::lua_xpush(self.ref_thread(), state, ExtraData::ERROR_TRACEBACK_IDX); ffi::lua_xpush(self.ref_thread(), state, ExtraData::ERROR_TRACEBACK_IDX);
// Lua 5.2+ support light C functions that does not require extra allocations // Lua 5.2+ support light C functions that does not require extra allocations
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_pushcfunction(state, crate::util::error_traceback); ffi::lua_pushcfunction(state, crate::util::error_traceback);
} }
@@ -928,7 +992,7 @@ impl RawLua {
// We generate metatable first to make sure it *always* available when userdata pushed // We generate metatable first to make sure it *always* available when userdata pushed
let mt_id = get_metatable_id()?; let mt_id = get_metatable_id()?;
let protect = !self.unlikely_memory_error(); let protect = !self.unlikely_memory_error();
crate::util::push_userdata(state, data, protect)?; push_userdata(state, data, protect)?;
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, mt_id); ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, mt_id);
ffi::lua_setmetatable(state, -2); ffi::lua_setmetatable(state, -2);
@@ -1056,6 +1120,18 @@ impl RawLua {
field_setters_index = Some(ffi::lua_absindex(state, -1)); field_setters_index = Some(ffi::lua_absindex(state, -1));
} }
// Create methods namecall table
#[cfg_attr(not(feature = "luau"), allow(unused_mut))]
let mut methods_map = None;
#[cfg(feature = "luau")]
if registry.enable_namecall {
let map: &mut rustc_hash::FxHashMap<_, crate::types::CallbackPtr> =
methods_map.get_or_insert_default();
for (k, m) in &registry.methods {
map.insert(k.as_bytes().to_vec(), &**m);
}
}
let mut methods_index = None; let mut methods_index = None;
let methods_nrec = registry.methods.len(); let methods_nrec = registry.methods.len();
#[cfg(feature = "async")] #[cfg(feature = "async")]
@@ -1103,6 +1179,7 @@ impl RawLua {
field_getters_index, field_getters_index,
field_setters_index, field_setters_index,
methods_index, methods_index,
methods_map,
)?; )?;
// Update stack guard to keep metatable after return // Update stack guard to keep metatable after return
@@ -1142,10 +1219,11 @@ impl RawLua {
Ok(type_id) => Ok(type_id), Ok(type_id) => Ok(type_id),
Err(Error::UserDataTypeMismatch) if ffi::lua_type(state, idx) != ffi::LUA_TUSERDATA => { Err(Error::UserDataTypeMismatch) if ffi::lua_type(state, idx) != ffi::LUA_TUSERDATA => {
// Report `FromLuaConversionError` instead // Report `FromLuaConversionError` instead
let idx_type_name = CStr::from_ptr(ffi::luaL_typename(state, idx)); let type_name = CStr::from_ptr(ffi::lua_typename(state, ffi::lua_type(state, idx)))
let idx_type_name = idx_type_name.to_str().unwrap(); .to_str()
.unwrap_or("unknown");
let message = format!("expected userdata of type '{}'", short_type_name::<T>()); let message = format!("expected userdata of type '{}'", short_type_name::<T>());
Err(Error::from_lua_conversion(idx_type_name, "userdata", message)) Err(Error::from_lua_conversion(type_name, "userdata", message))
} }
Err(err) => Err(err), Err(err) => Err(err),
} }
@@ -1226,7 +1304,13 @@ impl RawLua {
#[cfg(feature = "async")] #[cfg(feature = "async")]
pub(crate) fn create_async_callback(&self, func: AsyncCallback) -> Result<Function> { pub(crate) fn create_async_callback(&self, func: AsyncCallback) -> Result<Function> {
// Ensure that the coroutine library is loaded // Ensure that the coroutine library is loaded
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] #[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
unsafe { unsafe {
if !(*self.extra.get()).libs.contains(StdLib::COROUTINE) { if !(*self.extra.get()).libs.contains(StdLib::COROUTINE) {
load_std_libs(self.main_state(), StdLib::COROUTINE)?; load_std_libs(self.main_state(), StdLib::COROUTINE)?;
@@ -1234,7 +1318,7 @@ impl RawLua {
} }
} }
unsafe extern "C-unwind" fn call_callback(state: *mut ffi::lua_State) -> c_int { unsafe extern "C-unwind" fn get_future_callback(state: *mut ffi::lua_State) -> c_int {
// Async functions cannot be scoped and therefore destroyed, // Async functions cannot be scoped and therefore destroyed,
// so the first upvalue is always valid // so the first upvalue is always valid
let upvalue = get_userdata::<AsyncCallbackUpvalue>(state, ffi::lua_upvalueindex(1)); let upvalue = get_userdata::<AsyncCallbackUpvalue>(state, ffi::lua_upvalueindex(1));
@@ -1244,38 +1328,46 @@ impl RawLua {
let rawlua = (*extra).raw_lua(); let rawlua = (*extra).raw_lua();
let func = &*(*upvalue).data; let func = &*(*upvalue).data;
let fut = func(rawlua, nargs); let fut = Some(func(rawlua, nargs));
let extra = XRc::clone(&(*upvalue).extra); let extra = XRc::clone(&(*upvalue).extra);
let protect = !rawlua.unlikely_memory_error(); let protect = !rawlua.unlikely_memory_error();
push_internal_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?; push_internal_userdata(state, AsyncPollUpvalue { data: fut, extra }, protect)?;
if protect {
protect_lua!(state, 1, 1, fn(state) {
ffi::lua_pushcclosure(state, poll_future, 1);
})?;
} else {
ffi::lua_pushcclosure(state, poll_future, 1);
}
Ok(1) Ok(1)
}) })
} }
unsafe extern "C-unwind" fn poll_future(state: *mut ffi::lua_State) -> c_int { unsafe extern "C-unwind" fn poll_future(state: *mut ffi::lua_State) -> c_int {
let upvalue = get_userdata::<AsyncPollUpvalue>(state, ffi::lua_upvalueindex(1)); // Future is always passed in the first argument
callback_error_ext(state, (*upvalue).extra.get(), true, |extra, _| { let future = get_userdata::<AsyncPollUpvalue>(state, 1);
callback_error_ext(state, (*future).extra.get(), true, |extra, nargs| {
// Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments) // Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments)
// The lock must be already held as the future is polled // The lock must be already held as the future is polled
let rawlua = (*extra).raw_lua(); let rawlua = (*extra).raw_lua();
let fut = &mut (*upvalue).data; if nargs == 2 && ffi::lua_tolightuserdata(state, -1) == Lua::poll_terminate().0 {
// Destroy the future and terminate the Lua thread
(*future).data.take();
ffi::lua_pushinteger(state, -1);
return Ok(1);
}
let fut = &mut (*future).data;
let mut ctx = Context::from_waker(rawlua.waker()); let mut ctx = Context::from_waker(rawlua.waker());
match fut.as_mut().poll(&mut ctx) { match fut.as_mut().map(|fut| fut.as_mut().poll(&mut ctx)) {
Poll::Pending => { Some(Poll::Pending) => {
let fut_nvals = ffi::lua_gettop(state) - 1; // Exclude the future itself
if fut_nvals >= 3 && ffi::lua_tolightuserdata(state, -3) == Lua::poll_yield().0 {
// We have some values to yield
ffi::lua_pushnil(state);
ffi::lua_replace(state, -4);
return Ok(3);
}
ffi::lua_pushnil(state); ffi::lua_pushnil(state);
ffi::lua_pushlightuserdata(state, Lua::poll_pending().0); ffi::lua_pushlightuserdata(state, Lua::poll_pending().0);
Ok(2) Ok(2)
} }
Poll::Ready(nresults) => { Some(Poll::Ready(nresults)) => {
match nresults? { match nresults? {
nresults if nresults < 3 => { nresults if nresults < 3 => {
// Fast path for up to 2 results without creating a table // Fast path for up to 2 results without creating a table
@@ -1293,12 +1385,13 @@ impl RawLua {
} }
} }
} }
None => Err(Error::CallbackDestructed),
} }
}) })
} }
let state = self.state(); let state = self.state();
let get_poll = unsafe { let get_future = unsafe {
let _sg = StackGuard::new(state); let _sg = StackGuard::new(state);
check_stack(state, 4)?; check_stack(state, 4)?;
@@ -1308,10 +1401,10 @@ impl RawLua {
push_internal_userdata(state, upvalue, protect)?; push_internal_userdata(state, upvalue, protect)?;
if protect { if protect {
protect_lua!(state, 1, 1, fn(state) { protect_lua!(state, 1, 1, fn(state) {
ffi::lua_pushcclosure(state, call_callback, 1); ffi::lua_pushcclosure(state, get_future_callback, 1);
})?; })?;
} else { } else {
ffi::lua_pushcclosure(state, call_callback, 1); ffi::lua_pushcclosure(state, get_future_callback, 1);
} }
Function(self.pop_ref()) Function(self.pop_ref())
@@ -1330,16 +1423,19 @@ impl RawLua {
let coroutine = lua.globals().get::<Table>("coroutine")?; let coroutine = lua.globals().get::<Table>("coroutine")?;
// Prepare environment for the async poller // Prepare environment for the async poller
let env = lua.create_table_with_capacity(0, 3)?; let env = lua.create_table_with_capacity(0, 4)?;
env.set("get_poll", get_poll)?; env.set("get_future", get_future)?;
env.set("poll", unsafe { lua.create_c_function(poll_future)? })?;
env.set("yield", coroutine.get::<Function>("yield")?)?; env.set("yield", coroutine.get::<Function>("yield")?)?;
env.set("unpack", unsafe { lua.create_c_function(unpack)? })?; env.set("unpack", unsafe { lua.create_c_function(unpack)? })?;
lua.load( lua.load(
r#" r#"
local poll = get_poll(...) local poll, yield = poll, yield
local future = get_future(...)
local nres, res, res2 = poll(future)
while true do while true do
local nres, res, res2 = poll() -- Poll::Ready branch, `nres` is the number of results
if nres ~= nil then if nres ~= nil then
if nres == 0 then if nres == 0 then
return return
@@ -1347,11 +1443,28 @@ impl RawLua {
return res return res
elseif nres == 2 then elseif nres == 2 then
return res, res2 return res, res2
elseif nres < 0 then
-- Negative `nres` means that the future is terminated
-- It must stay yielded and never be resumed again
yield()
else else
return unpack(res, nres) return unpack(res, nres)
end end
end end
yield(res) -- `res` is a "pending" value
-- Poll::Pending branch
if res2 == nil then
-- `res` is a "pending" value
-- `yield` can return a signal to drop the future that we should propagate
-- to the poller
nres, res, res2 = poll(future, yield(res))
elseif res2 == 0 then
nres, res, res2 = poll(future, yield())
elseif res2 == 1 then
nres, res, res2 = poll(future, yield(res))
else
nres, res, res2 = poll(future, yield(unpack(res, res2)))
end
end end
"#, "#,
) )
@@ -1363,14 +1476,14 @@ impl RawLua {
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[inline] #[inline]
pub(crate) unsafe fn waker(&self) -> &Waker { pub(crate) fn waker(&self) -> &Waker {
(*self.extra.get()).waker.as_ref() unsafe { (*self.extra.get()).waker.as_ref() }
} }
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[inline] #[inline]
pub(crate) unsafe fn set_waker(&self, waker: NonNull<Waker>) -> NonNull<Waker> { pub(crate) fn set_waker(&self, waker: NonNull<Waker>) -> NonNull<Waker> {
mem::replace(&mut (*self.extra.get()).waker, waker) unsafe { mem::replace(&mut (*self.extra.get()).waker, waker) }
} }
} }
@@ -1410,7 +1523,13 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
#[cfg(feature = "luajit")] #[cfg(feature = "luajit")]
let _gc_guard = GcGuard::new(state); let _gc_guard = GcGuard::new(state);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] #[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
{ {
if libs.contains(StdLib::COROUTINE) { if libs.contains(StdLib::COROUTINE) {
requiref(state, ffi::LUA_COLIBNAME, ffi::luaopen_coroutine, 1)?; requiref(state, ffi::LUA_COLIBNAME, ffi::luaopen_coroutine, 1)?;
@@ -1434,7 +1553,7 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
requiref(state, ffi::LUA_STRLIBNAME, ffi::luaopen_string, 1)?; requiref(state, ffi::LUA_STRLIBNAME, ffi::luaopen_string, 1)?;
} }
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))]
{ {
if libs.contains(StdLib::UTF8) { if libs.contains(StdLib::UTF8) {
requiref(state, ffi::LUA_UTF8LIBNAME, ffi::luaopen_utf8, 1)?; requiref(state, ffi::LUA_UTF8LIBNAME, ffi::luaopen_utf8, 1)?;
+3 -31
View File
@@ -1,11 +1,11 @@
use std::os::raw::c_int; use std::os::raw::c_int;
use std::panic::{catch_unwind, AssertUnwindSafe}; use std::panic::{AssertUnwindSafe, catch_unwind};
use std::ptr; use std::ptr;
use std::sync::Arc; use std::sync::Arc;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::state::{ExtraData, RawLua}; use crate::state::{ExtraData, RawLua};
use crate::util::{self, get_internal_metatable, WrappedFailure}; use crate::util::{self, WrappedFailure, get_internal_metatable};
struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State); struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State);
@@ -89,7 +89,7 @@ where
PreallocatedFailure::New(_) => { PreallocatedFailure::New(_) => {
ffi::lua_rotate(state, 1, -1); ffi::lua_rotate(state, 1, -1);
ffi::lua_xmove(state, ref_thread, 1); ffi::lua_xmove(state, ref_thread, 1);
let index = ref_stack_pop(extra); let index = (*extra).ref_stack_pop();
(*extra).wrapped_failure_pool.push(index); (*extra).wrapped_failure_pool.push(index);
(*extra).wrapped_failure_top += 1; (*extra).wrapped_failure_top += 1;
} }
@@ -150,31 +150,3 @@ where
} }
} }
} }
pub(super) unsafe fn ref_stack_pop(extra: *mut ExtraData) -> c_int {
let extra = &mut *extra;
if let Some(free) = extra.ref_free.pop() {
ffi::lua_replace(extra.ref_thread, free);
return free;
}
// Try to grow max stack size
if extra.ref_stack_top >= extra.ref_stack_size {
let mut inc = extra.ref_stack_size; // Try to double stack size
while inc > 0 && ffi::lua_checkstack(extra.ref_thread, inc) == 0 {
inc /= 2;
}
if inc == 0 {
// Pop item on top of the stack to avoid stack leaking and successfully run destructors
// during unwinding.
ffi::lua_pop(extra.ref_thread, 1);
let top = extra.ref_stack_top;
// It is a user error to create enough references to exhaust the Lua max stack size for
// the ref thread.
panic!("cannot create a Lua reference, out of auxiliary stack space (used {top} slots)");
}
extra.ref_stack_size += inc;
}
extra.ref_stack_top += 1;
extra.ref_stack_top
}
+19 -4
View File
@@ -6,10 +6,22 @@ pub struct StdLib(u32);
impl StdLib { impl StdLib {
/// [`coroutine`](https://www.lua.org/manual/5.4/manual.html#6.2) library /// [`coroutine`](https://www.lua.org/manual/5.4/manual.html#6.2) library
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))] #[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
#[cfg_attr( #[cfg_attr(
docsrs, docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))) doc(cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
)))
)] )]
pub const COROUTINE: StdLib = StdLib(1); pub const COROUTINE: StdLib = StdLib(1);
@@ -28,8 +40,11 @@ impl StdLib {
pub const STRING: StdLib = StdLib(1 << 4); pub const STRING: StdLib = StdLib(1 << 4);
/// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library /// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))))] #[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau")))
)]
pub const UTF8: StdLib = StdLib(1 << 5); pub const UTF8: StdLib = StdLib(1 << 5);
/// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library /// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library
+49 -53
View File
@@ -2,7 +2,6 @@ use std::borrow::{Borrow, Cow};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::ops::Deref; use std::ops::Deref;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use std::string::String as StdString;
use std::{cmp, fmt, slice, str}; use std::{cmp, fmt, slice, str};
use crate::error::{Error, Result}; use crate::error::{Error, Result};
@@ -11,7 +10,7 @@ use crate::traits::IntoLua;
use crate::types::{LuaType, ValueRef}; use crate::types::{LuaType, ValueRef};
use crate::value::Value; use crate::value::Value;
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
use { use {
serde::ser::{Serialize, Serializer}, serde::ser::{Serialize, Serializer},
std::result::Result as StdResult, std::result::Result as StdResult,
@@ -21,37 +20,37 @@ use {
/// ///
/// Unlike Rust strings, Lua strings may not be valid UTF-8. /// Unlike Rust strings, Lua strings may not be valid UTF-8.
#[derive(Clone)] #[derive(Clone)]
pub struct String(pub(crate) ValueRef); pub struct LuaString(pub(crate) ValueRef);
impl String { impl LuaString {
/// Get a [`BorrowedStr`] if the Lua string is valid UTF-8. /// Get a [`BorrowedStr`] if the Lua string is valid UTF-8.
/// ///
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// # use mlua::{Lua, Result, String}; /// # use mlua::{Lua, LuaString, Result};
/// # fn main() -> Result<()> { /// # fn main() -> Result<()> {
/// # let lua = Lua::new(); /// # let lua = Lua::new();
/// let globals = lua.globals(); /// let globals = lua.globals();
/// ///
/// let version: String = globals.get("_VERSION")?; /// let version: LuaString = globals.get("_VERSION")?;
/// assert!(version.to_str()?.contains("Lua")); /// assert!(version.to_str()?.contains("Lua"));
/// ///
/// let non_utf8: String = lua.load(r#" "test\255" "#).eval()?; /// let non_utf8: LuaString = lua.load(r#" "test\255" "#).eval()?;
/// assert!(non_utf8.to_str().is_err()); /// assert!(non_utf8.to_str().is_err());
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline] #[inline]
pub fn to_str(&self) -> Result<BorrowedStr> { pub fn to_str(&self) -> Result<BorrowedStr<'_>> {
BorrowedStr::try_from(self) BorrowedStr::try_from(self)
} }
/// Converts this string to a [`StdString`]. /// Converts this Lua string to a [`String`].
/// ///
/// Any non-Unicode sequences are replaced with [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD]. /// Any non-Unicode sequences are replaced with [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD].
/// ///
/// This method returns [`StdString`] instead of [`Cow<'_, str>`] because lifetime cannot be /// This method returns [`String`] instead of [`Cow<'_, str>`] because lifetime cannot be
/// bound to a weak Lua object. /// bound to a weak Lua object.
/// ///
/// [U+FFFD]: std::char::REPLACEMENT_CHARACTER /// [U+FFFD]: std::char::REPLACEMENT_CHARACTER
@@ -70,11 +69,11 @@ impl String {
/// # } /// # }
/// ``` /// ```
#[inline] #[inline]
pub fn to_string_lossy(&self) -> StdString { pub fn to_string_lossy(&self) -> String {
StdString::from_utf8_lossy(&self.as_bytes()).into_owned() String::from_utf8_lossy(&self.as_bytes()).into_owned()
} }
/// Returns an object that implements [`Display`] for safely printing a Lua [`String`] that may /// Returns an object that implements [`Display`] for safely printing a [`LuaString`] that may
/// contain non-Unicode data. /// contain non-Unicode data.
/// ///
/// This may perform lossy conversion. /// This may perform lossy conversion.
@@ -86,35 +85,35 @@ impl String {
/// Get the bytes that make up this string. /// Get the bytes that make up this string.
/// ///
/// The returned slice will not contain the terminating nul byte, but will contain any nul /// The returned slice will not contain the terminating null byte, but will contain any null
/// bytes embedded into the Lua string. /// bytes embedded into the Lua string.
/// ///
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// # use mlua::{Lua, Result, String}; /// # use mlua::{Lua, LuaString, Result};
/// # fn main() -> Result<()> { /// # fn main() -> Result<()> {
/// # let lua = Lua::new(); /// # let lua = Lua::new();
/// let non_utf8: String = lua.load(r#" "test\255" "#).eval()?; /// let non_utf8: LuaString = lua.load(r#" "test\255" "#).eval()?;
/// assert!(non_utf8.to_str().is_err()); // oh no :( /// assert!(non_utf8.to_str().is_err()); // oh no :(
/// assert_eq!(non_utf8.as_bytes(), &b"test\xff"[..]); /// assert_eq!(non_utf8.as_bytes(), &b"test\xff"[..]);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline] #[inline]
pub fn as_bytes(&self) -> BorrowedBytes { pub fn as_bytes(&self) -> BorrowedBytes<'_> {
BorrowedBytes::from(self) BorrowedBytes::from(self)
} }
/// Get the bytes that make up this string, including the trailing nul byte. /// Get the bytes that make up this string, including the trailing null byte.
pub fn as_bytes_with_nul(&self) -> BorrowedBytes { pub fn as_bytes_with_nul(&self) -> BorrowedBytes<'_> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self); let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self);
// Include the trailing nul byte (it's always present but excluded by default) // Include the trailing null byte (it's always present but excluded by default)
let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) }; let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) };
BorrowedBytes { buf, borrow, _lua } BorrowedBytes { buf, borrow, _lua }
} }
// Does not return the terminating nul byte // Does not return the terminating null byte
unsafe fn to_slice(&self) -> (&[u8], Lua) { unsafe fn to_slice(&self) -> (&[u8], Lua) {
let lua = self.0.lua.upgrade(); let lua = self.0.lua.upgrade();
let slice = { let slice = {
@@ -135,7 +134,7 @@ impl String {
(slice, lua) (slice, lua)
} }
/// Converts this string to a generic C pointer. /// Converts this Lua string to a generic C pointer.
/// ///
/// There is no way to convert the pointer back to its original value. /// There is no way to convert the pointer back to its original value.
/// ///
@@ -146,7 +145,7 @@ impl String {
} }
} }
impl fmt::Debug for String { impl fmt::Debug for LuaString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let bytes = self.as_bytes(); let bytes = self.as_bytes();
// Check if the string is valid utf8 // Check if the string is valid utf8
@@ -162,12 +161,12 @@ impl fmt::Debug for String {
// Lua strings are basically `&[u8]` slices, so implement `PartialEq` for anything resembling that. // Lua strings are basically `&[u8]` slices, so implement `PartialEq` for anything resembling that.
// //
// This makes our `String` comparable with `Vec<u8>`, `[u8]`, `&str` and `String`. // This makes our `LuaString` comparable with `Vec<u8>`, `[u8]`, `&str` and `String`.
// //
// The only downside is that this disallows a comparison with `Cow<str>`, as that only implements // The only downside is that this disallows a comparison with `Cow<str>`, as that only implements
// `AsRef<str>`, which collides with this impl. Requiring `AsRef<str>` would fix that, but limit us // `AsRef<str>`, which collides with this impl. Requiring `AsRef<str>` would fix that, but limit us
// in other ways. // in other ways.
impl<T> PartialEq<T> for String impl<T> PartialEq<T> for LuaString
where where
T: AsRef<[u8]> + ?Sized, T: AsRef<[u8]> + ?Sized,
{ {
@@ -176,43 +175,43 @@ where
} }
} }
impl PartialEq for String { impl PartialEq for LuaString {
fn eq(&self, other: &String) -> bool { fn eq(&self, other: &LuaString) -> bool {
self.as_bytes() == other.as_bytes() self.as_bytes() == other.as_bytes()
} }
} }
impl Eq for String {} impl Eq for LuaString {}
impl<T> PartialOrd<T> for String impl<T> PartialOrd<T> for LuaString
where where
T: AsRef<[u8]> + ?Sized, T: AsRef<[u8]> + ?Sized,
{ {
fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> { fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
self.as_bytes().partial_cmp(&other.as_ref()) <[u8]>::partial_cmp(&self.as_bytes(), other.as_ref())
} }
} }
impl PartialOrd for String { impl PartialOrd for LuaString {
fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> { fn partial_cmp(&self, other: &LuaString) -> Option<cmp::Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }
impl Ord for String { impl Ord for LuaString {
fn cmp(&self, other: &String) -> cmp::Ordering { fn cmp(&self, other: &LuaString) -> cmp::Ordering {
self.as_bytes().cmp(&other.as_bytes()) self.as_bytes().cmp(&other.as_bytes())
} }
} }
impl Hash for String { impl Hash for LuaString {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.as_bytes().hash(state); self.as_bytes().hash(state);
} }
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
impl Serialize for String { impl Serialize for LuaString {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
@@ -224,7 +223,7 @@ impl Serialize for String {
} }
} }
struct Display<'a>(&'a String); struct Display<'a>(&'a LuaString);
impl fmt::Display for Display<'_> { impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -237,7 +236,7 @@ impl fmt::Display for Display<'_> {
pub struct BorrowedStr<'a> { pub struct BorrowedStr<'a> {
// `buf` points to a readonly memory managed by Lua // `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a str, pub(crate) buf: &'a str,
pub(crate) borrow: Cow<'a, String>, pub(crate) borrow: Cow<'a, LuaString>,
pub(crate) _lua: Lua, pub(crate) _lua: Lua,
} }
@@ -302,17 +301,14 @@ impl Ord for BorrowedStr<'_> {
} }
} }
impl<'a> TryFrom<&'a String> for BorrowedStr<'a> { impl<'a> TryFrom<&'a LuaString> for BorrowedStr<'a> {
type Error = Error; type Error = Error;
#[inline] #[inline]
fn try_from(value: &'a String) -> Result<Self> { fn try_from(value: &'a LuaString) -> Result<Self> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(value); let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(value);
let buf = str::from_utf8(buf).map_err(|e| Error::FromLuaConversionError { let buf =
from: "string", str::from_utf8(buf).map_err(|e| Error::from_lua_conversion("string", "&str", e.to_string()))?;
to: "&str".to_string(),
message: Some(e.to_string()),
})?;
Ok(Self { buf, borrow, _lua }) Ok(Self { buf, borrow, _lua })
} }
} }
@@ -321,7 +317,7 @@ impl<'a> TryFrom<&'a String> for BorrowedStr<'a> {
pub struct BorrowedBytes<'a> { pub struct BorrowedBytes<'a> {
// `buf` points to a readonly memory managed by Lua // `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a [u8], pub(crate) buf: &'a [u8],
pub(crate) borrow: Cow<'a, String>, pub(crate) borrow: Cow<'a, LuaString>,
pub(crate) _lua: Lua, pub(crate) _lua: Lua,
} }
@@ -389,9 +385,9 @@ impl<'a> IntoIterator for &'a BorrowedBytes<'_> {
} }
} }
impl<'a> From<&'a String> for BorrowedBytes<'a> { impl<'a> From<&'a LuaString> for BorrowedBytes<'a> {
#[inline] #[inline]
fn from(value: &'a String) -> Self { fn from(value: &'a LuaString) -> Self {
let (buf, _lua) = unsafe { value.to_slice() }; let (buf, _lua) = unsafe { value.to_slice() };
let borrow = Cow::Borrowed(value); let borrow = Cow::Borrowed(value);
Self { buf, borrow, _lua } Self { buf, borrow, _lua }
@@ -400,7 +396,7 @@ impl<'a> From<&'a String> for BorrowedBytes<'a> {
struct WrappedString<T: AsRef<[u8]>>(T); struct WrappedString<T: AsRef<[u8]>>(T);
impl String { impl LuaString {
/// Wraps bytes, returning an opaque type that implements [`IntoLua`] trait. /// Wraps bytes, returning an opaque type that implements [`IntoLua`] trait.
/// ///
/// This function uses [`Lua::create_string`] under the hood. /// This function uses [`Lua::create_string`] under the hood.
@@ -415,7 +411,7 @@ impl<T: AsRef<[u8]>> IntoLua for WrappedString<T> {
} }
} }
impl LuaType for String { impl LuaType for LuaString {
const TYPE_ID: c_int = ffi::LUA_TSTRING; const TYPE_ID: c_int = ffi::LUA_TSTRING;
} }
@@ -424,9 +420,9 @@ mod assertions {
use super::*; use super::*;
#[cfg(not(feature = "send"))] #[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_any!(String: Send); static_assertions::assert_not_impl_any!(LuaString: Send);
#[cfg(feature = "send")] #[cfg(feature = "send")]
static_assertions::assert_impl_all!(String: Send, Sync); static_assertions::assert_impl_all!(LuaString: Send, Sync);
#[cfg(feature = "send")] #[cfg(feature = "send")]
static_assertions::assert_impl_all!(BorrowedBytes: Send, Sync); static_assertions::assert_impl_all!(BorrowedBytes: Send, Sync);
#[cfg(feature = "send")] #[cfg(feature = "send")]
+317 -86
View File
@@ -1,21 +1,179 @@
//! Lua table handling.
//!
//! Tables are Lua's primary data structure, used for arrays, dictionaries, objects, modules,
//! and more. This module provides types for creating and manipulating Lua tables from Rust.
//!
//! # Main Types
//!
//! - [`Table`] - A handle to a Lua table.
//! - [`TablePairs`] - An iterator over key-value pairs in a table.
//! - [`TableSequence`] - An iterator over the array (sequence) portion of a table.
//!
//! # Basic Operations
//!
//! Tables support key-value access similar to Rust's `HashMap`:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let table = lua.create_table()?;
//!
//! // Set and get values
//! table.set("key", "value")?;
//! let value: String = table.get("key")?;
//! assert_eq!(value, "value");
//!
//! // Keys and values can be any Lua-compatible type
//! table.set(1, "first")?;
//! table.set("nested", lua.create_table()?)?;
//! # Ok(())
//! # }
//! ```
//!
//! # Array Operations
//!
//! Tables can be used as arrays with 1-based indexing:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let array = lua.create_table()?;
//!
//! // Push values to the end (like Vec::push)
//! array.push("first")?;
//! array.push("second")?;
//! array.push("third")?;
//!
//! // Pop from the end
//! let last: String = array.pop()?;
//! assert_eq!(last, "third");
//!
//! // Get length
//! assert_eq!(array.raw_len(), 2);
//! # Ok(())
//! # }
//! ```
//!
//! # Iteration
//!
//! Iterate over all key-value pairs with [`Table::pairs`]:
//!
//! ```
//! # use mlua::{Lua, Result, Value};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let table = lua.create_table()?;
//! table.set("a", 1)?;
//! table.set("b", 2)?;
//!
//! for pair in table.pairs::<String, i32>() {
//! let (key, value) = pair?;
//! println!("{key} = {value}");
//! }
//! # Ok(())
//! # }
//! ```
//!
//! For array portions, use [`Table::sequence_values`]:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let array = lua.create_sequence_from(["a", "b", "c"])?;
//!
//! for value in array.sequence_values::<String>() {
//! println!("{}", value?);
//! }
//! # Ok(())
//! # }
//! ```
//!
//! # Raw vs Normal Access
//!
//! Methods prefixed with `raw_` (like [`Table::raw_get`], [`Table::raw_set`]) bypass
//! metamethods, directly accessing the table's contents. Normal methods may trigger
//! `__index`, `__newindex`, and other metamethods:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//!
//! // raw_set bypasses __newindex metamethod
//! let t = lua.create_table()?;
//! t.raw_set("key", "value")?;
//!
//! // raw_get bypasses __index metamethod
//! let v: String = t.raw_get("key")?;
//! # Ok(())
//! # }
//! ```
//!
//! # Metatables
//!
//! Tables can have metatables that customize their behavior:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//!
//! let table = lua.create_table()?;
//! let metatable = lua.create_table()?;
//!
//! // Set a default value via __index
//! metatable.set("__index", lua.create_function(|_, _: ()| Ok("default"))?)?;
//! table.set_metatable(Some(metatable))?;
//!
//! // Accessing missing keys returns "default"
//! let value: String = table.get("missing")?;
//! assert_eq!(value, "default");
//! # Ok(())
//! # }
//! ```
//!
//! # Global Table
//!
//! The Lua global environment is itself a table, accessible via [`Lua::globals`]:
//!
//! ```
//! # use mlua::{Lua, Result};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let globals = lua.globals();
//!
//! // Set a global variable
//! globals.set("my_var", 42)?;
//!
//! // Now accessible from Lua code
//! let result: i32 = lua.load("my_var + 8").eval()?;
//! assert_eq!(result, 50);
//! # Ok(())
//! # }
//! ```
//!
//! [`Lua::globals`]: crate::Lua::globals
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt; use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::os::raw::{c_int, c_void}; use std::os::raw::c_void;
use std::string::String as StdString;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::function::Function; use crate::function::Function;
use crate::state::{LuaGuard, RawLua}; use crate::state::{LuaGuard, RawLua, WeakLua};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
use crate::types::{Integer, LuaType, ValueRef}; use crate::types::{Integer, ValueRef};
use crate::util::{assert_stack, check_stack, get_metatable_ptr, StackGuard}; use crate::util::{StackGuard, assert_stack, check_stack, get_metatable_ptr};
use crate::value::{Nil, Value}; use crate::value::{Nil, Value};
#[cfg(feature = "async")] #[cfg(feature = "async")]
use futures_util::future::{self, Either, Future}; use crate::function::AsyncCallFuture;
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
use { use {
rustc_hash::FxHashSet, rustc_hash::FxHashSet,
serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}, serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer},
@@ -211,7 +369,7 @@ impl Table {
/// ///
/// let always_equals_mt = lua.create_table()?; /// let always_equals_mt = lua.create_table()?;
/// always_equals_mt.set("__eq", lua.create_function(|_, (_t1, _t2): (Table, Table)| Ok(true))?)?; /// always_equals_mt.set("__eq", lua.create_function(|_, (_t1, _t2): (Table, Table)| Ok(true))?)?;
/// table2.set_metatable(Some(always_equals_mt)); /// table2.set_metatable(Some(always_equals_mt))?;
/// ///
/// assert!(table1.equals(&table1.clone())?); /// assert!(table1.equals(&table1.clone())?);
/// assert!(table1.equals(&table2)?); /// assert!(table1.equals(&table2)?);
@@ -226,15 +384,15 @@ impl Table {
// Compare using `__eq` metamethod if exists // Compare using `__eq` metamethod if exists
// First, check the self for the metamethod. // First, check the self for the metamethod.
// If self does not define it, then check the other table. // If self does not define it, then check the other table.
if let Some(mt) = self.metatable() { if let Some(mt) = self.metatable()
if mt.contains_key("__eq")? { && let Some(eq_func) = mt.get::<Option<Function>>("__eq")?
return mt.get::<Function>("__eq")?.call((self, other)); {
} return eq_func.call((self, other));
} }
if let Some(mt) = other.metatable() { if let Some(mt) = other.metatable()
if mt.contains_key("__eq")? { && let Some(eq_func) = mt.get::<Option<Function>>("__eq")?
return mt.get::<Function>("__eq")?.call((self, other)); {
} return eq_func.call((self, other));
} }
Ok(false) Ok(false)
@@ -416,14 +574,7 @@ impl Table {
lua.push_ref(&self.0); lua.push_ref(&self.0);
// Clear array part // This is safe as long as we don't assign new keys
for i in 1..=ffi::lua_rawlen(state, -1) {
ffi::lua_pushnil(state);
ffi::lua_rawseti(state, -2, i as Integer);
}
// Clear hash part
// It must be safe as long as we don't use invalid keys
ffi::lua_pushnil(state); ffi::lua_pushnil(state);
while ffi::lua_next(state, -2) != 0 { while ffi::lua_next(state, -2) != 0 {
ffi::lua_pop(state, 1); // pop value ffi::lua_pop(state, 1); // pop value
@@ -487,16 +638,12 @@ impl Table {
/// [`getmetatable`]: https://www.lua.org/manual/5.4/manual.html#pdf-getmetatable /// [`getmetatable`]: https://www.lua.org/manual/5.4/manual.html#pdf-getmetatable
pub fn metatable(&self) -> Option<Table> { pub fn metatable(&self) -> Option<Table> {
let lua = self.0.lua.lock(); let lua = self.0.lua.lock();
let state = lua.state(); let ref_thread = lua.ref_thread();
unsafe { unsafe {
let _sg = StackGuard::new(state); if ffi::lua_getmetatable(ref_thread, self.0.index) == 0 {
assert_stack(state, 2);
lua.push_ref(&self.0);
if ffi::lua_getmetatable(state, -1) == 0 {
None None
} else { } else {
Some(Table(lua.pop_ref())) Some(Table(lua.pop_ref_thread()))
} }
} }
} }
@@ -505,27 +652,23 @@ impl Table {
/// ///
/// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does /// If `metatable` is `None`, the metatable is removed (if no metatable is set, this does
/// nothing). /// nothing).
pub fn set_metatable(&self, metatable: Option<Table>) { pub fn set_metatable(&self, metatable: Option<Table>) -> Result<()> {
// Workaround to throw readonly error without returning Result
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
if self.is_readonly() { if self.is_readonly() {
panic!("attempt to modify a readonly table"); return Err(Error::runtime("attempt to modify a readonly table"));
} }
let lua = self.0.lua.lock(); let lua = self.0.lua.lock();
let state = lua.state(); let ref_thread = lua.ref_thread();
unsafe { unsafe {
let _sg = StackGuard::new(state); if let Some(metatable) = &metatable {
assert_stack(state, 2); ffi::lua_pushvalue(ref_thread, metatable.0.index);
lua.push_ref(&self.0);
if let Some(metatable) = metatable {
lua.push_ref(&metatable.0);
} else { } else {
ffi::lua_pushnil(state); ffi::lua_pushnil(ref_thread);
} }
ffi::lua_setmetatable(state, -2); ffi::lua_setmetatable(ref_thread, self.0.index);
} }
Ok(())
} }
/// Returns true if the table has metatable attached. /// Returns true if the table has metatable attached.
@@ -613,7 +756,7 @@ impl Table {
/// ``` /// ```
/// ///
/// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next /// [Lua manual]: http://www.lua.org/manual/5.4/manual.html#pdf-next
pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<K, V> { pub fn pairs<K: FromLua, V: FromLua>(&self) -> TablePairs<'_, K, V> {
TablePairs { TablePairs {
guard: self.0.lua.lock(), guard: self.0.lua.lock(),
table: self, table: self,
@@ -678,21 +821,30 @@ impl Table {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn sequence_values<V: FromLua>(&self) -> TableSequence<V> { pub fn sequence_values<V: FromLua>(&self) -> TableSequence<'_, V> {
TableSequence { TableSequence {
guard: self.0.lua.lock(), guard: self.0.lua.lock(),
table: self, table: self,
index: 1, index: 1,
len: None,
_phantom: PhantomData, _phantom: PhantomData,
} }
} }
/// Iterates over the sequence part of the table, invoking the given closure on each value. /// Iterates over the sequence part of the table, invoking the given closure on each value.
///
/// This methods is similar to [`Table::sequence_values`], but optimized for performance.
#[doc(hidden)] #[doc(hidden)]
pub fn for_each_value<V>(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()> pub fn for_each_value<V: FromLua>(&self, f: impl FnMut(V) -> Result<()>) -> Result<()> {
where self.for_each_value_by_len(None, f)
V: FromLua, }
{
fn for_each_value_by_len<V: FromLua>(
&self,
len: impl Into<Option<usize>>,
mut f: impl FnMut(V) -> Result<()>,
) -> Result<()> {
let len = len.into();
let lua = self.0.lua.lock(); let lua = self.0.lua.lock();
let state = lua.state(); let state = lua.state();
unsafe { unsafe {
@@ -700,9 +852,14 @@ impl Table {
check_stack(state, 4)?; check_stack(state, 4)?;
lua.push_ref(&self.0); lua.push_ref(&self.0);
let len = ffi::lua_rawlen(state, -1); for i in 1.. {
for i in 1..=len { if len.map(|len| i > len).unwrap_or(false) {
ffi::lua_rawgeti(state, -1, i as _); break;
}
let t = ffi::lua_rawgeti(state, -1, i as _);
if len.is_none() && t == ffi::LUA_TNIL {
break;
}
f(V::from_stack(-1, &lua)?)?; f(V::from_stack(-1, &lua)?)?;
ffi::lua_pop(state, 1); ffi::lua_pop(state, 1);
} }
@@ -735,8 +892,9 @@ impl Table {
Ok(()) Ok(())
} }
#[cfg(feature = "serialize")] /// Checks if the table has the array metatable attached.
pub(crate) fn is_array(&self) -> bool { #[cfg(feature = "serde")]
fn has_array_metatable(&self) -> bool {
let lua = self.0.lua.lock(); let lua = self.0.lua.lock();
let state = lua.state(); let state = lua.state();
unsafe { unsafe {
@@ -752,6 +910,70 @@ impl Table {
} }
} }
/// If the table is an array, returns the number of non-nil elements and max index.
///
/// Returns `None` if the table is not an array.
///
/// This operation has O(n) complexity.
#[cfg(feature = "serde")]
fn find_array_len(&self) -> Option<(usize, usize)> {
let lua = self.0.lua.lock();
let ref_thread = lua.ref_thread();
unsafe {
let _sg = StackGuard::new(ref_thread);
let (mut count, mut max_index) = (0, 0);
ffi::lua_pushnil(ref_thread);
while ffi::lua_next(ref_thread, self.0.index) != 0 {
if ffi::lua_type(ref_thread, -2) != ffi::LUA_TNUMBER {
return None;
}
let k = ffi::lua_tonumber(ref_thread, -2);
if k.trunc() != k || k < 1.0 {
return None;
}
max_index = std::cmp::max(max_index, k as usize);
count += 1;
ffi::lua_pop(ref_thread, 1);
}
Some((count, max_index))
}
}
/// Determines if the table should be encoded as an array or a map.
///
/// The algorithm is the following:
/// 1. If `detect_mixed_tables` is enabled, iterate over all keys in the table checking is they
/// all are positive integers. If non-array key is found, return `None` (encode as map).
/// Otherwise check the sparsity of the array. Too sparse arrays are encoded as maps.
///
/// 2. If `detect_mixed_tables` is disabled, check if the table has a positive length or has the
/// array metatable. If so, encode as array. If the table is empty and
/// `encode_empty_tables_as_array` is enabled, encode as array.
///
/// Returns the length of the array if it should be encoded as an array.
#[cfg(feature = "serde")]
pub(crate) fn encode_as_array(&self, options: crate::serde::de::Options) -> Option<usize> {
if options.detect_mixed_tables {
if let Some((len, max_idx)) = self.find_array_len() {
// If the array is too sparse, serialize it as a map instead
if len < 10 || len * 2 >= max_idx {
return Some(max_idx);
}
}
} else {
let len = self.raw_len();
if len > 0 || self.has_array_metatable() {
return Some(len);
}
if options.encode_empty_tables_as_array && self.is_empty() {
return Some(0);
}
}
None
}
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
#[inline(always)] #[inline(always)]
fn check_readonly_write(&self, lua: &RawLua) -> Result<()> { fn check_readonly_write(&self, lua: &RawLua) -> Result<()> {
@@ -871,10 +1093,6 @@ where
} }
} }
impl LuaType for Table {
const TYPE_ID: c_int = ffi::LUA_TTABLE;
}
impl ObjectLike for Table { impl ObjectLike for Table {
#[inline] #[inline]
fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> { fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
@@ -892,16 +1110,16 @@ impl ObjectLike for Table {
R: FromLuaMulti, R: FromLuaMulti,
{ {
// Convert table to a function and call via pcall that respects the `__call` metamethod. // Convert table to a function and call via pcall that respects the `__call` metamethod.
Function(self.0.copy()).call(args) Function(self.0.clone()).call(args)
} }
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[inline] #[inline]
fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>> fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where where
R: FromLuaMulti, R: FromLuaMulti,
{ {
Function(self.0.copy()).call_async(args) Function(self.0.clone()).call_async(args)
} }
#[inline] #[inline]
@@ -913,7 +1131,7 @@ impl ObjectLike for Table {
} }
#[cfg(feature = "async")] #[cfg(feature = "async")]
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>> fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where where
R: FromLuaMulti, R: FromLuaMulti,
{ {
@@ -933,35 +1151,45 @@ impl ObjectLike for Table {
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[inline] #[inline]
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>> fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where where
R: FromLuaMulti, R: FromLuaMulti,
{ {
match self.get(name) { match self.get(name) {
Ok(Value::Function(func)) => Either::Left(func.call_async(args)), Ok(Value::Function(func)) => func.call_async(args),
Ok(val) => { Ok(val) => {
let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
Either::Right(future::ready(Err(Error::RuntimeError(msg)))) AsyncCallFuture::error(Error::RuntimeError(msg))
} }
Err(err) => Either::Right(future::ready(Err(err))), Err(err) => AsyncCallFuture::error(err),
} }
} }
#[inline] #[inline]
fn to_string(&self) -> Result<StdString> { fn to_string(&self) -> Result<String> {
Value::Table(Table(self.0.copy())).to_string() Value::Table(Table(self.0.clone())).to_string()
}
#[inline]
fn to_value(&self) -> Value {
Value::Table(self.clone())
}
#[inline]
fn weak_lua(&self) -> &WeakLua {
&self.0.lua
} }
} }
/// A wrapped [`Table`] with customized serialization behavior. /// A wrapped [`Table`] with customized serialization behavior.
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
pub(crate) struct SerializableTable<'a> { pub(crate) struct SerializableTable<'a> {
table: &'a Table, table: &'a Table,
options: crate::serde::de::Options, options: crate::serde::de::Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>, visited: Rc<RefCell<FxHashSet<*const c_void>>>,
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
impl Serialize for Table { impl Serialize for Table {
#[inline] #[inline]
fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> { fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
@@ -969,7 +1197,7 @@ impl Serialize for Table {
} }
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
impl<'a> SerializableTable<'a> { impl<'a> SerializableTable<'a> {
#[inline] #[inline]
pub(crate) fn new( pub(crate) fn new(
@@ -985,13 +1213,22 @@ impl<'a> SerializableTable<'a> {
} }
} }
#[cfg(feature = "serialize")] impl<V> TableSequence<'_, V> {
/// Sets the length (hint) of the sequence.
#[cfg(feature = "serde")]
pub(crate) fn with_len(mut self, len: usize) -> Self {
self.len = Some(len);
self
}
}
#[cfg(feature = "serde")]
impl Serialize for SerializableTable<'_> { impl Serialize for SerializableTable<'_> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
use crate::serde::de::{check_value_for_skip, MapPairs, RecursionGuard}; use crate::serde::de::{MapPairs, RecursionGuard, check_value_for_skip};
use crate::value::SerializableValue; use crate::value::SerializableValue;
let convert_result = |res: Result<()>, serialize_err: Option<S::Error>| match res { let convert_result = |res: Result<()>, serialize_err: Option<S::Error>| match res {
@@ -1006,14 +1243,10 @@ impl Serialize for SerializableTable<'_> {
let _guard = RecursionGuard::new(self.table, visited); let _guard = RecursionGuard::new(self.table, visited);
// Array // Array
let len = self.table.raw_len(); if let Some(len) = self.table.encode_as_array(self.options) {
if len > 0
|| self.table.is_array()
|| (self.options.encode_empty_tables_as_array && self.table.is_empty())
{
let mut seq = serializer.serialize_seq(Some(len))?; let mut seq = serializer.serialize_seq(Some(len))?;
let mut serialize_err = None; let mut serialize_err = None;
let res = self.table.for_each_value::<Value>(|value| { let res = self.table.for_each_value_by_len::<Value>(len, |value| {
let skip = check_value_for_skip(&value, self.options, visited) let skip = check_value_for_skip(&value, self.options, visited)
.map_err(|err| Error::SerializeError(err.to_string()))?; .map_err(|err| Error::SerializeError(err.to_string()))?;
if skip { if skip {
@@ -1023,7 +1256,7 @@ impl Serialize for SerializableTable<'_> {
seq.serialize_element(&SerializableValue::new(&value, options, Some(visited))) seq.serialize_element(&SerializableValue::new(&value, options, Some(visited)))
.map_err(|err| { .map_err(|err| {
serialize_err = Some(err); serialize_err = Some(err);
Error::SerializeError(StdString::new()) Error::SerializeError(String::new())
}) })
}); });
convert_result(res, serialize_err)?; convert_result(res, serialize_err)?;
@@ -1048,7 +1281,7 @@ impl Serialize for SerializableTable<'_> {
) )
.map_err(|err| { .map_err(|err| {
serialize_err = Some(err); serialize_err = Some(err);
Error::SerializeError(StdString::new()) Error::SerializeError(String::new())
}) })
}; };
@@ -1137,13 +1370,11 @@ pub struct TableSequence<'a, V> {
guard: LuaGuard, guard: LuaGuard,
table: &'a Table, table: &'a Table,
index: Integer, index: Integer,
len: Option<usize>,
_phantom: PhantomData<V>, _phantom: PhantomData<V>,
} }
impl<V> Iterator for TableSequence<'_, V> impl<V: FromLua> Iterator for TableSequence<'_, V> {
where
V: FromLua,
{
type Item = Result<V>; type Item = Result<V>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
@@ -1157,7 +1388,7 @@ where
lua.push_ref(&self.table.0); lua.push_ref(&self.table.0);
match ffi::lua_rawgeti(state, -1, self.index) { match ffi::lua_rawgeti(state, -1, self.index) {
ffi::LUA_TNIL => None, ffi::LUA_TNIL if self.index as usize > self.len.unwrap_or(0) => None,
_ => { _ => {
self.index += 1; self.index += 1;
Some(V::from_stack(-1, lua)) Some(V::from_stack(-1, lua))
+70 -35
View File
@@ -6,11 +6,11 @@ use crate::function::Function;
use crate::state::RawLua; use crate::state::RawLua;
use crate::traits::{FromLuaMulti, IntoLuaMulti}; use crate::traits::{FromLuaMulti, IntoLuaMulti};
use crate::types::{LuaType, ValueRef}; use crate::types::{LuaType, ValueRef};
use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard}; use crate::util::{StackGuard, check_stack, error_traceback_thread, pop_error};
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
use crate::{ use crate::{
hook::{Debug, HookTriggers}, debug::{Debug, HookTriggers},
types::HookKind, types::HookKind,
}; };
@@ -86,13 +86,15 @@ unsafe impl Sync for Thread {}
#[must_use = "futures do nothing unless you `.await` or poll them"] #[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncThread<R> { pub struct AsyncThread<R> {
thread: Thread, thread: Thread,
ret: PhantomData<R>, ret: PhantomData<fn() -> R>,
recycle: bool, recycle: bool,
} }
impl Thread { impl Thread {
/// Returns reference to the Lua state that this thread is associated with.
#[doc(hidden)]
#[inline(always)] #[inline(always)]
fn state(&self) -> *mut ffi::lua_State { pub fn state(&self) -> *mut ffi::lua_State {
self.1 self.1
} }
@@ -154,7 +156,6 @@ impl Thread {
let thread_state = self.state(); let thread_state = self.state();
unsafe { unsafe {
let _sg = StackGuard::new(state); let _sg = StackGuard::new(state);
let _thread_sg = StackGuard::with_top(thread_state, 0);
let nargs = args.push_into_stack_multi(&lua)?; let nargs = args.push_into_stack_multi(&lua)?;
if nargs > 0 { if nargs > 0 {
@@ -163,6 +164,7 @@ impl Thread {
pushed_nargs += nargs; pushed_nargs += nargs;
} }
let _thread_sg = StackGuard::with_top(thread_state, 0);
let (_, nresults) = self.resume_inner(&lua, pushed_nargs)?; let (_, nresults) = self.resume_inner(&lua, pushed_nargs)?;
check_stack(state, nresults + 1)?; check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults); ffi::lua_xmove(thread_state, state, nresults);
@@ -190,12 +192,12 @@ impl Thread {
let thread_state = self.state(); let thread_state = self.state();
unsafe { unsafe {
let _sg = StackGuard::new(state); let _sg = StackGuard::new(state);
let _thread_sg = StackGuard::with_top(thread_state, 0);
check_stack(state, 1)?; check_stack(state, 1)?;
error.push_into_stack(&lua)?; error.push_into_stack(&lua)?;
ffi::lua_xmove(state, thread_state, 1); ffi::lua_xmove(state, thread_state, 1);
let _thread_sg = StackGuard::with_top(thread_state, 0);
let (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?; let (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?;
check_stack(state, nresults + 1)?; check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults); ffi::lua_xmove(thread_state, state, nresults);
@@ -263,11 +265,13 @@ impl Thread {
/// You can have multiple hooks for different threads. /// You can have multiple hooks for different threads.
/// ///
/// To remove a hook call [`Thread::remove_hook`]. /// To remove a hook call [`Thread::remove_hook`].
///
/// [`Lua::set_hook`]: crate::Lua::set_hook
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(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) -> Result<()>
where where
F: Fn(&crate::Lua, Debug) -> Result<crate::VmState> + crate::MaybeSend + 'static, F: Fn(&crate::Lua, &Debug) -> Result<crate::VmState> + crate::MaybeSend + 'static,
{ {
let lua = self.0.lua.lock(); let lua = self.0.lua.lock();
unsafe { unsafe {
@@ -305,29 +309,10 @@ impl Thread {
pub fn reset(&self, func: Function) -> Result<()> { pub fn reset(&self, func: Function) -> Result<()> {
let lua = self.0.lua.lock(); let lua = self.0.lua.lock();
let thread_state = self.state(); let thread_state = self.state();
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::Finished => {}
#[cfg(not(any(feature = "lua54", feature = "luau")))]
_ => return Err(Error::runtime("cannot reset non-finished thread")),
#[cfg(any(feature = "lua54", feature = "luau"))]
_ => unsafe {
#[cfg(all(feature = "lua54", not(feature = "vendored")))]
let status = ffi::lua_resetthread(thread_state);
#[cfg(all(feature = "lua54", feature = "vendored"))]
let status = ffi::lua_closethread(thread_state, lua.state());
#[cfg(feature = "lua54")]
if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status));
}
#[cfg(feature = "luau")]
ffi::lua_resetthread(thread_state);
},
}
unsafe { unsafe {
let status = self.status_inner(&lua);
self.reset_inner(status)?;
// Push function to the top of the thread stack // Push function to the top of the thread stack
ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index); ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index);
@@ -342,6 +327,42 @@ impl Thread {
} }
} }
unsafe fn reset_inner(&self, status: ThreadStatusInner) -> Result<()> {
match status {
ThreadStatusInner::New(_) => {
// The thread is new, so we can just set the top to 0
ffi::lua_settop(self.state(), 0);
Ok(())
}
ThreadStatusInner::Running => Err(Error::runtime("cannot reset a running thread")),
ThreadStatusInner::Finished => Ok(()),
#[cfg(not(any(feature = "lua55", feature = "lua54", feature = "luau")))]
ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => {
Err(Error::runtime("cannot reset non-finished thread"))
}
#[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))]
ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => {
let thread_state = self.state();
#[cfg(all(feature = "lua54", not(feature = "vendored")))]
let status = ffi::lua_resetthread(thread_state);
#[cfg(any(feature = "lua55", all(feature = "lua54", feature = "vendored")))]
let status = {
let lua = self.0.lua.lock();
ffi::lua_closethread(thread_state, lua.state())
};
#[cfg(any(feature = "lua55", feature = "lua54"))]
if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status));
}
#[cfg(feature = "luau")]
ffi::lua_resetthread(thread_state);
Ok(())
}
}
}
/// Converts [`Thread`] to an [`AsyncThread`] which implements [`Future`] and [`Stream`] traits. /// Converts [`Thread`] to an [`AsyncThread`] which implements [`Future`] and [`Stream`] traits.
/// ///
/// Only resumable threads can be converted to [`AsyncThread`]. /// Only resumable threads can be converted to [`AsyncThread`].
@@ -453,7 +474,6 @@ impl Thread {
/// ``` /// ```
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[doc(hidden)]
pub fn sandbox(&self) -> Result<()> { pub fn sandbox(&self) -> Result<()> {
let lua = self.0.lua.lock(); let lua = self.0.lua.lock();
let state = lua.state(); let state = lua.state();
@@ -503,10 +523,25 @@ impl<R> AsyncThread<R> {
#[cfg(feature = "async")] #[cfg(feature = "async")]
impl<R> Drop for AsyncThread<R> { impl<R> Drop for AsyncThread<R> {
fn drop(&mut self) { fn drop(&mut self) {
#[allow(clippy::collapsible_if)]
if self.recycle { if self.recycle {
if let Some(lua) = self.thread.0.lua.try_lock() { if let Some(lua) = self.thread.0.lua.try_lock() {
// For Lua 5.4 this also closes all pending to-be-closed variables unsafe {
unsafe { lua.recycle_thread(&mut self.thread) }; let mut status = self.thread.status_inner(&lua);
if matches!(status, ThreadStatusInner::Yielded(0)) {
// The thread is dropped while yielded, resume it with the "terminate" signal
ffi::lua_pushlightuserdata(self.thread.1, crate::Lua::poll_terminate().0);
if let Ok((new_status, _)) = self.thread.resume_inner(&lua, 1) {
// `new_status` should always be `ThreadStatusInner::Yielded(0)`
status = new_status;
}
}
// For Lua 5.4 this also closes all pending to-be-closed variables
if self.thread.reset_inner(status).is_ok() {
lua.recycle_thread(&mut self.thread);
}
}
} }
} }
} }
@@ -570,7 +605,7 @@ impl<R: FromLuaMulti> Future for AsyncThread<R> {
if status.is_yielded() { if status.is_yielded() {
if !(nresults == 1 && is_poll_pending(thread_state)) { if !(nresults == 1 && is_poll_pending(thread_state)) {
// Ignore value returned via yield() // Ignore values returned via yield()
cx.waker().wake_by_ref(); cx.waker().wake_by_ref();
} }
return Poll::Pending; return Poll::Pending;
@@ -601,7 +636,7 @@ struct WakerGuard<'lua, 'a> {
impl<'lua, 'a> WakerGuard<'lua, 'a> { impl<'lua, 'a> WakerGuard<'lua, 'a> {
#[inline] #[inline]
pub fn new(lua: &'lua RawLua, waker: &'a Waker) -> Result<WakerGuard<'lua, 'a>> { pub fn new(lua: &'lua RawLua, waker: &'a Waker) -> Result<WakerGuard<'lua, 'a>> {
let prev = unsafe { lua.set_waker(NonNull::from(waker)) }; let prev = lua.set_waker(NonNull::from(waker));
Ok(WakerGuard { Ok(WakerGuard {
lua, lua,
prev, prev,
@@ -613,7 +648,7 @@ impl<'lua, 'a> WakerGuard<'lua, 'a> {
#[cfg(feature = "async")] #[cfg(feature = "async")]
impl Drop for WakerGuard<'_, '_> { impl Drop for WakerGuard<'_, '_> {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { self.lua.set_waker(self.prev) }; self.lua.set_waker(self.prev);
} }
} }
+48 -9
View File
@@ -1,17 +1,16 @@
use std::os::raw::c_int; use std::os::raw::c_int;
use std::string::String as StdString;
use std::sync::Arc; use std::sync::Arc;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::multi::MultiValue; use crate::multi::MultiValue;
use crate::private::Sealed; use crate::private::Sealed;
use crate::state::{Lua, RawLua}; use crate::state::{Lua, RawLua, WeakLua};
use crate::types::MaybeSend; use crate::types::MaybeSend;
use crate::util::{check_stack, short_type_name}; use crate::util::{check_stack, parse_lookup_path, short_type_name};
use crate::value::Value; use crate::value::Value;
#[cfg(feature = "async")] #[cfg(feature = "async")]
use std::future::Future; use {crate::function::AsyncCallFuture, std::future::Future};
/// Trait for types convertible to [`Value`]. /// Trait for types convertible to [`Value`].
pub trait IntoLua: Sized { pub trait IntoLua: Sized {
@@ -162,7 +161,7 @@ pub trait ObjectLike: Sealed {
/// arguments. /// arguments.
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>> fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where where
R: FromLuaMulti; R: FromLuaMulti;
@@ -178,7 +177,7 @@ pub trait ObjectLike: Sealed {
/// This might invoke the `__index` metamethod. /// This might invoke the `__index` metamethod.
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>> fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where where
R: FromLuaMulti; R: FromLuaMulti;
@@ -196,14 +195,54 @@ pub trait ObjectLike: Sealed {
/// This might invoke the `__index` metamethod. /// This might invoke the `__index` metamethod.
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>> fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where where
R: FromLuaMulti; R: FromLuaMulti;
/// Look up a value by a path of keys.
///
/// The syntax is similar to accessing nested tables in Lua, with additional support for
/// `?` operator to perform safe navigation.
///
/// For example, the path `a[1].c` is equivalent to `table.a[1].c` in Lua.
/// With `?` operator, `a[1]?.c` is equivalent to `table.a[1] and table.a[1].c or nil` in Lua.
///
/// Bracket notation rules:
/// - `[123]` - integer keys
/// - `["string key"]` or `['string key']` - string keys (must be quoted)
/// - String keys support escape sequences: `\"`, `\'`, `\\`
fn get_path<V: FromLua>(&self, path: &str) -> Result<V> {
let mut current = self.to_value();
for (key, safe_nil) in parse_lookup_path(path)? {
current = match current {
Value::Table(table) => table.get::<Value>(key),
Value::UserData(ud) => ud.get::<Value>(key),
_ => {
let type_name = current.type_name();
let err = format!("attempt to index a {type_name} value with key '{key}'");
Err(Error::runtime(err))
}
}?;
if safe_nil && (current == Value::Nil || current == Value::NULL) {
break;
}
}
let lua = self.weak_lua().lock();
V::from_lua(current, lua.lua())
}
/// Converts the object to a string in a human-readable format. /// Converts the object to a string in a human-readable format.
/// ///
/// This might invoke the `__tostring` metamethod. /// This might invoke the `__tostring` metamethod.
fn to_string(&self) -> Result<StdString>; fn to_string(&self) -> Result<String>;
/// Converts the object to a Lua value.
fn to_value(&self) -> Value;
/// Gets a reference to the associated Lua state.
#[doc(hidden)]
fn weak_lua(&self) -> &WeakLua;
} }
/// A trait for types that can be used as Lua functions. /// A trait for types that can be used as Lua functions.
@@ -299,7 +338,7 @@ impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
pub(crate) trait ShortTypeName { pub(crate) trait ShortTypeName {
#[inline(always)] #[inline(always)]
fn type_name() -> StdString { fn type_name() -> String {
short_type_name::<Self>() short_type_name::<Self>()
} }
} }
+18 -9
View File
@@ -1,9 +1,9 @@
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use crate::error::Result;
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
use crate::hook::{Debug, HookTriggers}; use crate::debug::{Debug, HookTriggers};
use crate::error::Result;
use crate::state::{ExtraData, Lua, RawLua}; use crate::state::{ExtraData, Lua, RawLua};
// Re-export mutex wrappers // Re-export mutex wrappers
@@ -20,6 +20,9 @@ pub use either::Either;
pub use registry_key::RegistryKey; pub use registry_key::RegistryKey;
pub(crate) use value_ref::ValueRef; pub(crate) use value_ref::ValueRef;
#[cfg(feature = "async")]
pub(crate) use value_ref::ValueRefIndex;
/// Type of Lua integer numbers. /// Type of Lua integer numbers.
pub type Integer = ffi::lua_Integer; pub type Integer = ffi::lua_Integer;
/// Type of Lua floating point numbers. /// Type of Lua floating point numbers.
@@ -35,10 +38,13 @@ unsafe impl Send for LightUserData {}
unsafe impl Sync for LightUserData {} unsafe impl Sync for LightUserData {}
#[cfg(feature = "send")] #[cfg(feature = "send")]
pub(crate) type Callback = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + Send + 'static>; type CallbackFn<'a> = dyn Fn(&RawLua, c_int) -> Result<c_int> + Send + 'a;
#[cfg(not(feature = "send"))] #[cfg(not(feature = "send"))]
pub(crate) type Callback = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + 'static>; type CallbackFn<'a> = dyn Fn(&RawLua, c_int) -> Result<c_int> + 'a;
pub(crate) type Callback = Box<CallbackFn<'static>>;
pub(crate) type CallbackPtr = *const CallbackFn<'static>;
pub(crate) type ScopedCallback<'s> = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + 's>; pub(crate) type ScopedCallback<'s> = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + 's>;
@@ -61,7 +67,7 @@ pub(crate) type AsyncCallback =
pub(crate) type AsyncCallbackUpvalue = Upvalue<AsyncCallback>; pub(crate) type AsyncCallbackUpvalue = Upvalue<AsyncCallback>;
#[cfg(feature = "async")] #[cfg(feature = "async")]
pub(crate) type AsyncPollUpvalue = Upvalue<BoxFuture<'static, Result<c_int>>>; pub(crate) type AsyncPollUpvalue = Upvalue<Option<BoxFuture<'static, Result<c_int>>>>;
/// Type to set next Lua VM action after executing interrupt or hook function. /// Type to set next Lua VM action after executing interrupt or hook function.
pub enum VmState { pub enum VmState {
@@ -79,10 +85,10 @@ pub(crate) enum HookKind {
} }
#[cfg(all(feature = "send", not(feature = "luau")))] #[cfg(all(feature = "send", not(feature = "luau")))]
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, Debug) -> Result<VmState> + Send>; pub(crate) type HookCallback = XRc<dyn Fn(&Lua, &Debug) -> Result<VmState> + Send>;
#[cfg(all(not(feature = "send"), not(feature = "luau")))] #[cfg(all(not(feature = "send"), not(feature = "luau")))]
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, Debug) -> Result<VmState>>; pub(crate) type HookCallback = XRc<dyn Fn(&Lua, &Debug) -> Result<VmState>>;
#[cfg(all(feature = "send", feature = "luau"))] #[cfg(all(feature = "send", feature = "luau"))]
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState> + Send>; pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState> + Send>;
@@ -102,10 +108,12 @@ pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData) + Se
#[cfg(all(not(feature = "send"), feature = "luau"))] #[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData)>; pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData)>;
#[cfg(all(feature = "send", feature = "lua54"))] #[cfg(feature = "send")]
#[cfg(any(feature = "lua55", feature = "lua54"))]
pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()> + Send>; pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()> + Send>;
#[cfg(all(not(feature = "send"), feature = "lua54"))] #[cfg(not(feature = "send"))]
#[cfg(any(feature = "lua55", feature = "lua54"))]
pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()>>; pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()>>;
/// A trait that adds `Send` requirement if `send` feature is enabled. /// A trait that adds `Send` requirement if `send` feature is enabled.
@@ -114,6 +122,7 @@ pub trait MaybeSend: Send {}
#[cfg(feature = "send")] #[cfg(feature = "send")]
impl<T: Send> MaybeSend for T {} impl<T: Send> MaybeSend for T {}
/// A trait that adds `Send` requirement if `send` feature is enabled.
#[cfg(not(feature = "send"))] #[cfg(not(feature = "send"))]
pub trait MaybeSend {} pub trait MaybeSend {}
#[cfg(not(feature = "send"))] #[cfg(not(feature = "send"))]
+4 -4
View File
@@ -43,7 +43,7 @@ impl AppData {
#[inline] #[inline]
#[track_caller] #[track_caller]
pub(crate) fn borrow<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRef<T>> { pub(crate) fn borrow<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRef<'_, T>> {
match self.try_borrow(guard) { match self.try_borrow(guard) {
Ok(data) => data, Ok(data) => data,
Err(err) => panic!("already mutably borrowed: {err:?}"), Err(err) => panic!("already mutably borrowed: {err:?}"),
@@ -53,7 +53,7 @@ impl AppData {
pub(crate) fn try_borrow<T: 'static>( pub(crate) fn try_borrow<T: 'static>(
&self, &self,
guard: Option<LuaGuard>, guard: Option<LuaGuard>,
) -> Result<Option<AppDataRef<T>>, BorrowError> { ) -> Result<Option<AppDataRef<'_, T>>, BorrowError> {
let data = unsafe { &*self.container.get() } let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>()) .get(&TypeId::of::<T>())
.map(|c| c.try_borrow()) .map(|c| c.try_borrow())
@@ -74,7 +74,7 @@ impl AppData {
#[inline] #[inline]
#[track_caller] #[track_caller]
pub(crate) fn borrow_mut<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRefMut<T>> { pub(crate) fn borrow_mut<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRefMut<'_, T>> {
match self.try_borrow_mut(guard) { match self.try_borrow_mut(guard) {
Ok(data) => data, Ok(data) => data,
Err(err) => panic!("already borrowed: {err:?}"), Err(err) => panic!("already borrowed: {err:?}"),
@@ -84,7 +84,7 @@ impl AppData {
pub(crate) fn try_borrow_mut<T: 'static>( pub(crate) fn try_borrow_mut<T: 'static>(
&self, &self,
guard: Option<LuaGuard>, guard: Option<LuaGuard>,
) -> Result<Option<AppDataRefMut<T>>, BorrowMutError> { ) -> Result<Option<AppDataRefMut<'_, T>>, BorrowMutError> {
let data = unsafe { &*self.container.get() } let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>()) .get(&TypeId::of::<T>())
.map(|c| c.try_borrow_mut()) .map(|c| c.try_borrow_mut())
+2 -2
View File
@@ -31,7 +31,7 @@ mod inner {
} }
#[inline(always)] #[inline(always)]
pub(crate) fn lock(&self) -> ReentrantMutexGuard<T> { pub(crate) fn lock(&self) -> ReentrantMutexGuard<'_, T> {
ReentrantMutexGuard(&self.0) ReentrantMutexGuard(&self.0)
} }
@@ -69,7 +69,7 @@ mod inner {
#[inline(always)] #[inline(always)]
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 .0 &self.0.0
} }
} }
} }
+28 -23
View File
@@ -1,22 +1,39 @@
use std::fmt; use std::fmt;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use super::XRc;
use crate::state::{RawLua, WeakLua}; use crate::state::{RawLua, WeakLua};
/// A reference to a Lua (complex) value stored in the Lua auxiliary thread. /// A reference to a Lua (complex) value stored in the Lua auxiliary thread.
#[derive(Clone)]
pub struct ValueRef { pub struct ValueRef {
pub(crate) lua: WeakLua, pub(crate) lua: WeakLua,
// Keep index separate to avoid additional indirection when accessing it.
pub(crate) index: c_int, pub(crate) index: c_int,
pub(crate) drop: bool, // If `index_count` is `None`, the value does not need to be destroyed.
pub(crate) index_count: Option<ValueRefIndex>,
}
/// A reference to a Lua value index in the auxiliary thread.
/// It's cheap to clone and can be used to track the number of references to a value.
#[derive(Clone)]
pub(crate) struct ValueRefIndex(pub(crate) XRc<c_int>);
impl From<c_int> for ValueRefIndex {
#[inline]
fn from(index: c_int) -> Self {
ValueRefIndex(XRc::new(index))
}
} }
impl ValueRef { impl ValueRef {
#[inline] #[inline]
pub(crate) fn new(lua: &RawLua, index: c_int) -> Self { pub(crate) fn new(lua: &RawLua, index: impl Into<ValueRefIndex>) -> Self {
let index = index.into();
ValueRef { ValueRef {
lua: lua.weak().clone(), lua: lua.weak().clone(),
index, index: *index.0,
drop: true, index_count: Some(index),
} }
} }
@@ -25,16 +42,6 @@ impl ValueRef {
let lua = self.lua.lock(); let lua = self.lua.lock();
unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) } unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) }
} }
/// Returns a copy of the value, which is valid as long as the original value is held.
#[inline]
pub(crate) fn copy(&self) -> Self {
ValueRef {
lua: self.lua.clone(),
index: self.index,
drop: false,
}
}
} }
impl fmt::Debug for ValueRef { impl fmt::Debug for ValueRef {
@@ -43,17 +50,15 @@ impl fmt::Debug for ValueRef {
} }
} }
impl Clone for ValueRef {
fn clone(&self) -> Self {
unsafe { self.lua.lock().clone_ref(self) }
}
}
impl Drop for ValueRef { impl Drop for ValueRef {
fn drop(&mut self) { fn drop(&mut self) {
if self.drop { if let Some(ValueRefIndex(index)) = self.index_count.take() {
if let Some(lua) = self.lua.try_lock() { // It's guaranteed that the inner value returns exactly once.
unsafe { lua.drop_ref(self) }; // This means in particular that the value is not dropped.
if XRc::into_inner(index).is_some()
&& let Some(lua) = self.lua.try_lock()
{
unsafe { lua.drop_ref(self) }
} }
} }
} }
+187 -86
View File
@@ -3,22 +3,21 @@ use std::ffi::CStr;
use std::fmt; use std::fmt;
use std::hash::Hash; use std::hash::Hash;
use std::os::raw::{c_char, c_void}; use std::os::raw::{c_char, c_void};
use std::string::String as StdString;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::function::Function; use crate::function::Function;
use crate::state::Lua; use crate::state::Lua;
use crate::string::String; use crate::string::LuaString;
use crate::table::{Table, TablePairs}; use crate::table::{Table, TablePairs};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{MaybeSend, ValueRef}; use crate::types::{MaybeSend, ValueRef};
use crate::util::{check_stack, get_userdata, push_string, take_userdata, StackGuard}; use crate::util::{StackGuard, check_stack, get_userdata, push_string, short_type_name, take_userdata};
use crate::value::Value; use crate::value::Value;
#[cfg(feature = "async")] #[cfg(feature = "async")]
use std::future::Future; use std::future::Future;
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
use { use {
serde::ser::{self, Serialize, Serializer}, serde::ser::{self, Serialize, Serializer},
std::result::Result as StdResult, std::result::Result as StdResult,
@@ -30,8 +29,8 @@ pub use r#ref::{UserDataRef, UserDataRefMut};
pub use registry::UserDataRegistry; pub use registry::UserDataRegistry;
pub(crate) use registry::{RawUserDataRegistry, UserDataProxy}; pub(crate) use registry::{RawUserDataRegistry, UserDataProxy};
pub(crate) use util::{ pub(crate) use util::{
borrow_userdata_scoped, borrow_userdata_scoped_mut, collect_userdata, init_userdata_metatable, TypeIdHints, borrow_userdata_scoped, borrow_userdata_scoped_mut, collect_userdata,
TypeIdHints, init_userdata_metatable,
}; };
/// Kinds of metamethods that can be overridden. /// Kinds of metamethods that can be overridden.
@@ -56,32 +55,53 @@ pub enum MetaMethod {
/// The unary minus (`-`) operator. /// The unary minus (`-`) operator.
Unm, Unm,
/// The floor division (//) operator. /// The floor division (//) operator.
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))))] #[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau")))
)]
IDiv, IDiv,
/// The bitwise AND (&) operator. /// The bitwise AND (&) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] #[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
BAnd, BAnd,
/// The bitwise OR (|) operator. /// The bitwise OR (|) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] #[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
BOr, BOr,
/// The bitwise XOR (binary ~) operator. /// The bitwise XOR (binary ~) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] #[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
BXor, BXor,
/// The bitwise NOT (unary ~) operator. /// The bitwise NOT (unary ~) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] #[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
BNot, BNot,
/// The bitwise left shift (<<) operator. /// The bitwise left shift (<<) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] #[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
Shl, Shl,
/// The bitwise right shift (>>) operator. /// The bitwise right shift (>>) operator.
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53"))))] #[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua55", feature = "lua54", feature = "lua53")))
)]
Shr, Shr,
/// The string concatenation operator `..`. /// The string concatenation operator `..`.
Concat, Concat,
@@ -106,10 +126,22 @@ pub enum MetaMethod {
/// The `__pairs` metamethod. /// The `__pairs` metamethod.
/// ///
/// This is not an operator, but it will be called by the built-in `pairs` function. /// 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(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
))]
#[cfg_attr( #[cfg_attr(
docsrs, docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))) doc(cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
)))
)] )]
Pairs, Pairs,
/// The `__ipairs` metamethod. /// The `__ipairs` metamethod.
@@ -135,8 +167,8 @@ pub enum MetaMethod {
/// [documentation][lua_doc]. /// [documentation][lua_doc].
/// ///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#3.3.8 /// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#3.3.8
#[cfg(feature = "lua54")] #[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))] #[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
Close, Close,
/// The `__name`/`__type` metafield. /// The `__name`/`__type` metafield.
/// ///
@@ -152,7 +184,7 @@ impl PartialEq<MetaMethod> for &str {
} }
} }
impl PartialEq<MetaMethod> for StdString { impl PartialEq<MetaMethod> for String {
fn eq(&self, other: &MetaMethod) -> bool { fn eq(&self, other: &MetaMethod) -> bool {
self == other.name() self == other.name()
} }
@@ -176,19 +208,19 @@ impl MetaMethod {
MetaMethod::Pow => "__pow", MetaMethod::Pow => "__pow",
MetaMethod::Unm => "__unm", MetaMethod::Unm => "__unm",
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))]
MetaMethod::IDiv => "__idiv", MetaMethod::IDiv => "__idiv",
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
MetaMethod::BAnd => "__band", MetaMethod::BAnd => "__band",
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
MetaMethod::BOr => "__bor", MetaMethod::BOr => "__bor",
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
MetaMethod::BXor => "__bxor", MetaMethod::BXor => "__bxor",
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
MetaMethod::BNot => "__bnot", MetaMethod::BNot => "__bnot",
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
MetaMethod::Shl => "__shl", MetaMethod::Shl => "__shl",
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
MetaMethod::Shr => "__shr", MetaMethod::Shr => "__shr",
MetaMethod::Concat => "__concat", MetaMethod::Concat => "__concat",
@@ -201,14 +233,20 @@ impl MetaMethod {
MetaMethod::Call => "__call", MetaMethod::Call => "__call",
MetaMethod::ToString => "__tostring", MetaMethod::ToString => "__tostring",
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] #[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
))]
MetaMethod::Pairs => "__pairs", MetaMethod::Pairs => "__pairs",
#[cfg(any(feature = "lua52", feature = "luajit52"))] #[cfg(any(feature = "lua52", feature = "luajit52"))]
MetaMethod::IPairs => "__ipairs", MetaMethod::IPairs => "__ipairs",
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
MetaMethod::Iter => "__iter", MetaMethod::Iter => "__iter",
#[cfg(feature = "lua54")] #[cfg(any(feature = "lua55", feature = "lua54"))]
MetaMethod::Close => "__close", MetaMethod::Close => "__close",
#[rustfmt::skip] #[rustfmt::skip]
@@ -240,6 +278,13 @@ impl AsRef<str> for MetaMethod {
} }
} }
impl From<MetaMethod> for String {
#[inline]
fn from(method: MetaMethod) -> Self {
method.name().to_owned()
}
}
/// Method registry for [`UserData`] implementors. /// Method registry for [`UserData`] implementors.
pub trait UserDataMethods<T> { pub trait UserDataMethods<T> {
/// Add a regular method which accepts a `&T` as the first parameter. /// Add a regular method which accepts a `&T` as the first parameter.
@@ -249,7 +294,7 @@ pub trait UserDataMethods<T> {
/// ///
/// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will /// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will
/// be used as a fall-back if no regular method is found. /// be used as a fall-back if no regular method is found.
fn add_method<M, A, R>(&mut self, name: impl ToString, method: M) fn add_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
where where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static, M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
@@ -260,12 +305,35 @@ pub trait UserDataMethods<T> {
/// Refer to [`add_method`] for more information about the implementation. /// Refer to [`add_method`] for more information about the implementation.
/// ///
/// [`add_method`]: UserDataMethods::add_method /// [`add_method`]: UserDataMethods::add_method
fn add_method_mut<M, A, R>(&mut self, name: impl ToString, method: M) fn add_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
where where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static, M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
R: IntoLuaMulti; R: IntoLuaMulti;
/// Add a method which accepts `T` as the first parameter.
///
/// The userdata `T` will be moved out of the userdata container. This is useful for
/// methods that need to consume the userdata.
///
/// The method can be called only once per userdata instance, subsequent calls will result in a
/// [`Error::UserDataDestructed`] error.
#[doc(hidden)]
fn add_method_once<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
M: Fn(&Lua, T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.into();
let method_name = format!("{}.{name}", short_type_name::<T>());
self.add_function(name, move |lua, (ud, args): (AnyUserData, A)| {
let this = (ud.take()).map_err(|err| Error::bad_self_argument(&method_name, err))?;
method(lua, this, args)
});
}
/// Add an async method which accepts a `&T` as the first parameter and returns [`Future`]. /// Add an async method which accepts a `&T` as the first parameter and returns [`Future`].
/// ///
/// Refer to [`add_method`] for more information about the implementation. /// Refer to [`add_method`] for more information about the implementation.
@@ -273,7 +341,7 @@ pub trait UserDataMethods<T> {
/// [`add_method`]: UserDataMethods::add_method /// [`add_method`]: UserDataMethods::add_method
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_method<M, A, MR, R>(&mut self, name: impl ToString, method: M) fn add_async_method<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where where
T: 'static, T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static, M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
@@ -288,7 +356,7 @@ pub trait UserDataMethods<T> {
/// [`add_method`]: UserDataMethods::add_method /// [`add_method`]: UserDataMethods::add_method
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M) fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where where
T: 'static, T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static, M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
@@ -296,12 +364,40 @@ pub trait UserDataMethods<T> {
MR: Future<Output = Result<R>> + MaybeSend + 'static, MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti; R: IntoLuaMulti;
/// Add an async method which accepts a `T` as the first parameter and returns [`Future`].
///
/// The userdata `T` will be moved out of the userdata container. This is useful for
/// methods that need to consume the userdata.
///
/// The method can be called only once per userdata instance, subsequent calls will result in a
/// [`Error::UserDataDestructed`] error.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[doc(hidden)]
fn add_async_method_once<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
M: Fn(Lua, T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti,
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = name.into();
let method_name = format!("{}.{name}", short_type_name::<T>());
self.add_async_function(name, move |lua, (ud, args): (AnyUserData, A)| {
match (ud.take()).map_err(|err| Error::bad_self_argument(&method_name, err)) {
Ok(this) => either::Either::Left(method(lua, this, args)),
Err(err) => either::Either::Right(async move { Err(err) }),
}
});
}
/// Add a regular method as a function which accepts generic arguments. /// Add a regular method as a function which accepts generic arguments.
/// ///
/// The first argument will be a [`AnyUserData`] of type `T` if the method is called with Lua /// The first argument will be a [`AnyUserData`] of type `T` if the method is called with Lua
/// method syntax: `my_userdata:my_method(arg1, arg2)`, or it is passed in as the first /// method syntax: `my_userdata:my_method(arg1, arg2)`, or it is passed in as the first
/// argument: `my_userdata.my_method(my_userdata, arg1, arg2)`. /// argument: `my_userdata.my_method(my_userdata, arg1, arg2)`.
fn add_function<F, A, R>(&mut self, name: impl ToString, function: F) fn add_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
where where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static, F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
@@ -312,7 +408,7 @@ pub trait UserDataMethods<T> {
/// This is a version of [`add_function`] that accepts a `FnMut` argument. /// This is a version of [`add_function`] that accepts a `FnMut` argument.
/// ///
/// [`add_function`]: UserDataMethods::add_function /// [`add_function`]: UserDataMethods::add_function
fn add_function_mut<F, A, R>(&mut self, name: impl ToString, function: F) fn add_function_mut<F, A, R>(&mut self, name: impl Into<String>, function: F)
where where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static, F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
@@ -326,7 +422,7 @@ pub trait UserDataMethods<T> {
/// [`add_function`]: UserDataMethods::add_function /// [`add_function`]: UserDataMethods::add_function
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_function<F, A, FR, R>(&mut self, name: impl ToString, function: F) fn add_async_function<F, A, FR, R>(&mut self, name: impl Into<String>, function: F)
where where
F: Fn(Lua, A) -> FR + MaybeSend + 'static, F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
@@ -341,7 +437,7 @@ pub trait UserDataMethods<T> {
/// side has a metatable. To prevent this, use [`add_meta_function`]. /// side has a metatable. To prevent this, use [`add_meta_function`].
/// ///
/// [`add_meta_function`]: UserDataMethods::add_meta_function /// [`add_meta_function`]: UserDataMethods::add_meta_function
fn add_meta_method<M, A, R>(&mut self, name: impl ToString, method: M) fn add_meta_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
where where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static, M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
@@ -355,7 +451,7 @@ pub trait UserDataMethods<T> {
/// side has a metatable. To prevent this, use [`add_meta_function`]. /// side has a metatable. To prevent this, use [`add_meta_function`].
/// ///
/// [`add_meta_function`]: UserDataMethods::add_meta_function /// [`add_meta_function`]: UserDataMethods::add_meta_function
fn add_meta_method_mut<M, A, R>(&mut self, name: impl ToString, method: M) fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
where where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static, M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
@@ -371,7 +467,7 @@ pub trait UserDataMethods<T> {
docsrs, docsrs,
doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))) doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau")))))
)] )]
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl ToString, method: M) fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where where
T: 'static, T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static, M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
@@ -387,7 +483,7 @@ pub trait UserDataMethods<T> {
/// [`add_meta_method_mut`]: UserDataMethods::add_meta_method_mut /// [`add_meta_method_mut`]: UserDataMethods::add_meta_method_mut
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M) fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where where
T: 'static, T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static, M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
@@ -400,7 +496,7 @@ pub trait UserDataMethods<T> {
/// Metamethods for binary operators can be triggered if either the left or right argument to /// Metamethods for binary operators can be triggered if either the left or right argument to
/// the binary operator has a metatable, so the first argument here is not necessarily a /// the binary operator has a metatable, so the first argument here is not necessarily a
/// userdata of type `T`. /// userdata of type `T`.
fn add_meta_function<F, A, R>(&mut self, name: impl ToString, function: F) fn add_meta_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
where where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static, F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
@@ -411,7 +507,7 @@ pub trait UserDataMethods<T> {
/// This is a version of [`add_meta_function`] that accepts a `FnMut` argument. /// This is a version of [`add_meta_function`] that accepts a `FnMut` argument.
/// ///
/// [`add_meta_function`]: UserDataMethods::add_meta_function /// [`add_meta_function`]: UserDataMethods::add_meta_function
fn add_meta_function_mut<F, A, R>(&mut self, name: impl ToString, function: F) fn add_meta_function_mut<F, A, R>(&mut self, name: impl Into<String>, function: F)
where where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static, F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
@@ -427,7 +523,7 @@ pub trait UserDataMethods<T> {
docsrs, docsrs,
doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))) doc(cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau")))))
)] )]
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl ToString, function: F) fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl Into<String>, function: F)
where where
F: Fn(Lua, A) -> FR + MaybeSend + 'static, F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
@@ -446,7 +542,7 @@ pub trait UserDataFields<T> {
/// ///
/// If `add_meta_method` is used to set the `__index` metamethod, it will /// If `add_meta_method` is used to set the `__index` metamethod, it will
/// be used as a fall-back if no regular field or method are found. /// be used as a fall-back if no regular field or method are found.
fn add_field<V>(&mut self, name: impl ToString, value: V) fn add_field<V>(&mut self, name: impl Into<String>, value: V)
where where
V: IntoLua + 'static; V: IntoLua + 'static;
@@ -457,7 +553,7 @@ pub trait UserDataFields<T> {
/// ///
/// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will /// If `add_meta_method` is used to set the `__index` metamethod, the `__index` metamethod will
/// be used as a fall-back if no regular field or method are found. /// be used as a fall-back if no regular field or method are found.
fn add_field_method_get<M, R>(&mut self, name: impl ToString, method: M) fn add_field_method_get<M, R>(&mut self, name: impl Into<String>, method: M)
where where
M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static, M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
R: IntoLua; R: IntoLua;
@@ -470,21 +566,21 @@ pub trait UserDataFields<T> {
/// ///
/// If `add_meta_method` is used to set the `__newindex` metamethod, the `__newindex` metamethod /// If `add_meta_method` is used to set the `__newindex` metamethod, the `__newindex` metamethod
/// will be used as a fall-back if no regular field is found. /// will be used as a fall-back if no regular field is found.
fn add_field_method_set<M, A>(&mut self, name: impl ToString, method: M) fn add_field_method_set<M, A>(&mut self, name: impl Into<String>, method: M)
where where
M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
A: FromLua; A: FromLua;
/// Add a regular field getter as a function which accepts a generic [`AnyUserData`] of type `T` /// Add a regular field getter as a function which accepts a generic [`AnyUserData`] of type `T`
/// argument. /// argument.
fn add_field_function_get<F, R>(&mut self, name: impl ToString, function: F) fn add_field_function_get<F, R>(&mut self, name: impl Into<String>, function: F)
where where
F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static, F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
R: IntoLua; R: IntoLua;
/// Add a regular field setter as a function which accepts a generic [`AnyUserData`] of type `T` /// Add a regular field setter as a function which accepts a generic [`AnyUserData`] of type `T`
/// first argument. /// first argument.
fn add_field_function_set<F, A>(&mut self, name: impl ToString, function: F) fn add_field_function_set<F, A>(&mut self, name: impl Into<String>, function: F)
where where
F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static, F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
A: FromLua; A: FromLua;
@@ -497,7 +593,7 @@ pub trait UserDataFields<T> {
/// ///
/// `mlua` will trigger an error on an attempt to define a protected metamethod, /// `mlua` will trigger an error on an attempt to define a protected metamethod,
/// like `__gc` or `__metatable`. /// like `__gc` or `__metatable`.
fn add_meta_field<V>(&mut self, name: impl ToString, value: V) fn add_meta_field<V>(&mut self, name: impl Into<String>, value: V)
where where
V: IntoLua + 'static; V: IntoLua + 'static;
@@ -509,7 +605,7 @@ pub trait UserDataFields<T> {
/// ///
/// `mlua` will trigger an error on an attempt to define a protected metamethod, /// `mlua` will trigger an error on an attempt to define a protected metamethod,
/// like `__gc` or `__metatable`. /// like `__gc` or `__metatable`.
fn add_meta_field_with<F, R>(&mut self, name: impl ToString, f: F) fn add_meta_field_with<F, R>(&mut self, name: impl Into<String>, f: F)
where where
F: FnOnce(&Lua) -> Result<R> + 'static, F: FnOnce(&Lua) -> Result<R> + 'static,
R: IntoLua; R: IntoLua;
@@ -621,6 +717,14 @@ impl AnyUserData {
matches!(type_id, Some(type_id) if type_id == TypeId::of::<T>()) matches!(type_id, Some(type_id) if type_id == TypeId::of::<T>())
} }
/// Checks whether the type of this userdata is a [proxy object] for `T`.
///
/// [proxy object]: crate::Lua::create_proxy
#[inline]
pub fn is_proxy<T: 'static>(&self) -> bool {
self.is::<UserDataProxy<T>>()
}
/// Borrow this userdata immutably if it is of type `T`. /// Borrow this userdata immutably if it is of type `T`.
/// ///
/// # Errors /// # Errors
@@ -683,22 +787,16 @@ impl AnyUserData {
/// Keeps associated user values unchanged (they will be collected by Lua's GC). /// Keeps associated user values unchanged (they will be collected by Lua's GC).
pub fn take<T: 'static>(&self) -> Result<T> { pub fn take<T: 'static>(&self) -> Result<T> {
let lua = self.0.lua.lock(); let lua = self.0.lua.lock();
let state = lua.state(); match lua.get_userdata_ref_type_id(&self.0)? {
unsafe { Some(type_id) if type_id == TypeId::of::<T>() => unsafe {
let _sg = StackGuard::new(state); let ref_thread = lua.ref_thread();
check_stack(state, 2)?; if (*get_userdata::<UserDataStorage<T>>(ref_thread, self.0.index)).has_exclusive_access() {
take_userdata::<UserDataStorage<T>>(ref_thread, self.0.index).into_inner()
let type_id = lua.push_userdata_ref(&self.0)?; } else {
match type_id { Err(Error::UserDataBorrowMutError)
Some(type_id) if type_id == TypeId::of::<T>() => {
if (*get_userdata::<UserDataStorage<T>>(state, -1)).has_exclusive_access() {
take_userdata::<UserDataStorage<T>>(state).into_inner()
} else {
Err(Error::UserDataBorrowMutError)
}
} }
_ => Err(Error::UserDataTypeMismatch), },
} _ => Err(Error::UserDataTypeMismatch),
} }
} }
@@ -887,16 +985,17 @@ impl AnyUserData {
self.raw_metatable().map(UserDataMetatable) self.raw_metatable().map(UserDataMetatable)
} }
/// Returns a raw metatable of this [`AnyUserData`].
fn raw_metatable(&self) -> Result<Table> { fn raw_metatable(&self) -> Result<Table> {
let lua = self.0.lua.lock(); let lua = self.0.lua.lock();
let state = lua.state(); let ref_thread = lua.ref_thread();
unsafe { unsafe {
let _sg = StackGuard::new(state); // Check that userdata is registered and not destructed
check_stack(state, 3)?; // All registered userdata types have a non-empty metatable
let _type_id = lua.get_userdata_ref_type_id(&self.0)?;
lua.push_userdata_ref(&self.0)?; ffi::lua_getmetatable(ref_thread, self.0.index);
ffi::lua_getmetatable(state, -1); // Checked that non-empty on the previous call Ok(Table(lua.pop_ref_thread()))
Ok(Table(lua.pop_ref()))
} }
} }
@@ -919,8 +1018,10 @@ impl AnyUserData {
lua.get_userdata_ref_type_id(&self.0).ok().flatten() lua.get_userdata_ref_type_id(&self.0).ok().flatten()
} }
/// Returns a type name of this `UserData` (from a metatable field). /// Returns a type name of this userdata (from a metatable field).
pub(crate) fn type_name(&self) -> Result<Option<StdString>> { ///
/// If no type name is set, returns `None`.
pub fn type_name(&self) -> Result<Option<String>> {
let lua = self.0.lua.lock(); let lua = self.0.lua.lock();
let state = lua.state(); let state = lua.state();
unsafe { unsafe {
@@ -937,7 +1038,7 @@ impl AnyUserData {
ffi::luaL_getmetafield(state, -1, MetaMethod::Type.as_cstr().as_ptr()) ffi::luaL_getmetafield(state, -1, MetaMethod::Type.as_cstr().as_ptr())
}; };
match name_type { match name_type {
ffi::LUA_TSTRING => Ok(Some(String(lua.pop_ref()).to_str()?.to_owned())), ffi::LUA_TSTRING => Ok(Some(LuaString(lua.pop_ref()).to_str()?.to_owned())),
_ => Ok(None), _ => Ok(None),
} }
} }
@@ -963,7 +1064,7 @@ impl AnyUserData {
/// Returns `true` if this [`AnyUserData`] is serializable (e.g. was created using /// Returns `true` if this [`AnyUserData`] is serializable (e.g. was created using
/// [`Lua::create_ser_userdata`]). /// [`Lua::create_ser_userdata`]).
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
pub(crate) fn is_serializable(&self) -> bool { pub(crate) fn is_serializable(&self) -> bool {
let lua = self.0.lua.lock(); let lua = self.0.lua.lock();
let is_serializable = || unsafe { let is_serializable = || unsafe {
@@ -1014,7 +1115,7 @@ impl UserDataMetatable {
/// The pairs are wrapped in a [`Result`], since they are lazily converted to `V` type. /// The pairs are wrapped in a [`Result`], since they are lazily converted to `V` type.
/// ///
/// [`Result`]: crate::Result /// [`Result`]: crate::Result
pub fn pairs<V: FromLua>(&self) -> UserDataMetatablePairs<V> { pub fn pairs<V: FromLua>(&self) -> UserDataMetatablePairs<'_, V> {
UserDataMetatablePairs(self.0.pairs()) UserDataMetatablePairs(self.0.pairs())
} }
} }
@@ -1024,13 +1125,13 @@ impl UserDataMetatable {
/// It skips restricted metamethods, such as `__gc` or `__metatable`. /// It skips restricted metamethods, such as `__gc` or `__metatable`.
/// ///
/// This struct is created by the [`UserDataMetatable::pairs`] method. /// This struct is created by the [`UserDataMetatable::pairs`] method.
pub struct UserDataMetatablePairs<'a, V>(TablePairs<'a, StdString, V>); pub struct UserDataMetatablePairs<'a, V>(TablePairs<'a, String, V>);
impl<V> Iterator for UserDataMetatablePairs<'_, V> impl<V> Iterator for UserDataMetatablePairs<'_, V>
where where
V: FromLua, V: FromLua,
{ {
type Item = Result<(StdString, V)>; type Item = Result<(String, V)>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
loop { loop {
@@ -1047,7 +1148,7 @@ where
} }
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
impl Serialize for AnyUserData { impl Serialize for AnyUserData {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where where
@@ -1078,8 +1179,8 @@ impl AnyUserData {
/// [`IntoLua`] trait. /// [`IntoLua`] trait.
/// ///
/// This function uses [`Lua::create_ser_any_userdata`] under the hood. /// This function uses [`Lua::create_ser_any_userdata`] under the hood.
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn wrap_ser<T: Serialize + MaybeSend + 'static>(data: T) -> impl IntoLua { pub fn wrap_ser<T: Serialize + MaybeSend + 'static>(data: T) -> impl IntoLua {
WrappedUserdata(move |lua| lua.create_ser_any_userdata(data)) WrappedUserdata(move |lua| lua.create_ser_any_userdata(data))
} }
+16 -16
View File
@@ -1,6 +1,6 @@
use std::cell::{RefCell, UnsafeCell}; use std::cell::{RefCell, UnsafeCell};
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
use serde::ser::{Serialize, Serializer}; use serde::ser::{Serialize, Serializer};
use crate::error::{Error, Result}; use crate::error::{Error, Result};
@@ -9,10 +9,10 @@ use crate::types::XRc;
use super::lock::{RawLock, UserDataLock}; use super::lock::{RawLock, UserDataLock};
use super::r#ref::{UserDataRef, UserDataRefMut}; use super::r#ref::{UserDataRef, UserDataRefMut};
#[cfg(all(feature = "serialize", not(feature = "send")))] #[cfg(all(feature = "serde", not(feature = "send")))]
type DynSerialize = dyn erased_serde::Serialize; type DynSerialize = dyn erased_serde::Serialize;
#[cfg(all(feature = "serialize", feature = "send"))] #[cfg(all(feature = "serde", feature = "send"))]
type DynSerialize = dyn erased_serde::Serialize + Send; type DynSerialize = dyn erased_serde::Serialize + Send;
pub(crate) enum UserDataStorage<T> { pub(crate) enum UserDataStorage<T> {
@@ -24,7 +24,7 @@ pub(crate) enum UserDataStorage<T> {
// It's stored inside a Lua VM and protected by the outer `ReentrantMutex`. // It's stored inside a Lua VM and protected by the outer `ReentrantMutex`.
pub(crate) enum UserDataVariant<T> { pub(crate) enum UserDataVariant<T> {
Default(XRc<UserDataCell<T>>), Default(XRc<UserDataCell<T>>),
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
Serializable(XRc<UserDataCell<Box<DynSerialize>>>, bool), // bool is `is_sync` Serializable(XRc<UserDataCell<Box<DynSerialize>>>, bool), // bool is `is_sync`
} }
@@ -33,7 +33,7 @@ impl<T> Clone for UserDataVariant<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
match self { match self {
Self::Default(inner) => Self::Default(XRc::clone(inner)), Self::Default(inner) => Self::Default(XRc::clone(inner)),
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
Self::Serializable(inner, is_sync) => Self::Serializable(XRc::clone(inner), *is_sync), Self::Serializable(inner, is_sync) => Self::Serializable(XRc::clone(inner), *is_sync),
} }
} }
@@ -79,7 +79,7 @@ impl<T> UserDataVariant<T> {
} }
Ok(match self { Ok(match self {
Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(), Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(),
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
Self::Serializable(inner, _) => unsafe { Self::Serializable(inner, _) => unsafe {
let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner()); let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner());
*Box::from_raw(raw as *mut T) *Box::from_raw(raw as *mut T)
@@ -91,7 +91,7 @@ impl<T> UserDataVariant<T> {
fn strong_count(&self) -> usize { fn strong_count(&self) -> usize {
match self { match self {
Self::Default(inner) => XRc::strong_count(inner), Self::Default(inner) => XRc::strong_count(inner),
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
Self::Serializable(inner, _) => XRc::strong_count(inner), Self::Serializable(inner, _) => XRc::strong_count(inner),
} }
} }
@@ -100,7 +100,7 @@ impl<T> UserDataVariant<T> {
pub(super) fn raw_lock(&self) -> &RawLock { pub(super) fn raw_lock(&self) -> &RawLock {
match self { match self {
Self::Default(inner) => &inner.raw_lock, Self::Default(inner) => &inner.raw_lock,
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
Self::Serializable(inner, _) => &inner.raw_lock, Self::Serializable(inner, _) => &inner.raw_lock,
} }
} }
@@ -109,13 +109,13 @@ impl<T> UserDataVariant<T> {
pub(super) fn as_ptr(&self) -> *mut T { pub(super) fn as_ptr(&self) -> *mut T {
match self { match self {
Self::Default(inner) => inner.value.get(), Self::Default(inner) => inner.value.get(),
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
Self::Serializable(inner, _) => unsafe { &mut **(inner.value.get() as *mut Box<T>) }, Self::Serializable(inner, _) => unsafe { &mut **(inner.value.get() as *mut Box<T>) },
} }
} }
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
impl Serialize for UserDataStorage<()> { impl Serialize for UserDataStorage<()> {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> { fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
match self { match self {
@@ -173,10 +173,10 @@ pub(crate) enum ScopedUserDataVariant<T> {
impl<T> Drop for ScopedUserDataVariant<T> { impl<T> Drop for ScopedUserDataVariant<T> {
#[inline] #[inline]
fn drop(&mut self) { fn drop(&mut self) {
if let Self::Boxed(value) = self { if let Self::Boxed(value) = self
if let Ok(value) = value.try_borrow_mut() { && let Ok(value) = value.try_borrow_mut()
unsafe { drop(Box::from_raw(*value)) }; {
} unsafe { drop(Box::from_raw(*value)) }
} }
} }
} }
@@ -197,7 +197,7 @@ impl<T: 'static> UserDataStorage<T> {
Self::Scoped(ScopedUserDataVariant::RefMut(RefCell::new(data))) Self::Scoped(ScopedUserDataVariant::RefMut(RefCell::new(data)))
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
#[inline(always)] #[inline(always)]
pub(crate) fn new_ser(data: T) -> Self pub(crate) fn new_ser(data: T) -> Self
where where
@@ -209,7 +209,7 @@ impl<T: 'static> UserDataStorage<T> {
Self::Owned(variant) Self::Owned(variant)
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
#[inline(always)] #[inline(always)]
pub(crate) fn is_serializable(&self) -> bool { pub(crate) fn is_serializable(&self) -> bool {
matches!(self, Self::Owned(UserDataVariant::Serializable(..))) matches!(self, Self::Owned(UserDataVariant::Serializable(..)))
+25 -16
View File
@@ -1,28 +1,27 @@
use std::string::String as StdString; use crate::Function;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::state::WeakLua;
use crate::table::Table; use crate::table::Table;
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
use crate::userdata::AnyUserData; use crate::userdata::AnyUserData;
use crate::value::Value; use crate::value::Value;
use crate::Function;
#[cfg(feature = "async")] #[cfg(feature = "async")]
use futures_util::future::{self, Either, Future}; use crate::function::AsyncCallFuture;
impl ObjectLike for AnyUserData { impl ObjectLike for AnyUserData {
#[inline] #[inline]
fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> { fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
// `lua_gettable` method used under the hood can work with any Lua value // `lua_gettable` method used under the hood can work with any Lua value
// that has `__index` metamethod // that has `__index` metamethod
Table(self.0.copy()).get_protected(key) Table(self.0.clone()).get_protected(key)
} }
#[inline] #[inline]
fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> { fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
// `lua_settable` method used under the hood can work with any Lua value // `lua_settable` method used under the hood can work with any Lua value
// that has `__newindex` metamethod // that has `__newindex` metamethod
Table(self.0.copy()).set_protected(key, value) Table(self.0.clone()).set_protected(key, value)
} }
#[inline] #[inline]
@@ -30,16 +29,16 @@ impl ObjectLike for AnyUserData {
where where
R: FromLuaMulti, R: FromLuaMulti,
{ {
Function(self.0.copy()).call(args) Function(self.0.clone()).call(args)
} }
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[inline] #[inline]
fn call_async<R>(&self, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>> fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where where
R: FromLuaMulti, R: FromLuaMulti,
{ {
Function(self.0.copy()).call_async(args) Function(self.0.clone()).call_async(args)
} }
#[inline] #[inline]
@@ -51,7 +50,7 @@ impl ObjectLike for AnyUserData {
} }
#[cfg(feature = "async")] #[cfg(feature = "async")]
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>> fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where where
R: FromLuaMulti, R: FromLuaMulti,
{ {
@@ -72,22 +71,32 @@ impl ObjectLike for AnyUserData {
} }
#[cfg(feature = "async")] #[cfg(feature = "async")]
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> impl Future<Output = Result<R>> fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where where
R: FromLuaMulti, R: FromLuaMulti,
{ {
match self.get(name) { match self.get(name) {
Ok(Value::Function(func)) => Either::Left(func.call_async(args)), Ok(Value::Function(func)) => func.call_async(args),
Ok(val) => { Ok(val) => {
let msg = format!("attempt to call a {} value (function '{name}')", val.type_name()); let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
Either::Right(future::ready(Err(Error::RuntimeError(msg)))) AsyncCallFuture::error(Error::RuntimeError(msg))
} }
Err(err) => Either::Right(future::ready(Err(err))), Err(err) => AsyncCallFuture::error(err),
} }
} }
#[inline] #[inline]
fn to_string(&self) -> Result<StdString> { fn to_string(&self) -> Result<String> {
Value::UserData(AnyUserData(self.0.copy())).to_string() Value::UserData(self.clone()).to_string()
}
#[inline]
fn to_value(&self) -> Value {
Value::UserData(self.clone())
}
#[inline]
fn weak_lua(&self) -> &WeakLua {
&self.0.lua
} }
} }
+7 -7
View File
@@ -1,4 +1,4 @@
use std::any::{type_name, TypeId}; use std::any::{TypeId, type_name};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::os::raw::c_int; use std::os::raw::c_int;
use std::{fmt, mem}; use std::{fmt, mem};
@@ -63,7 +63,7 @@ impl<T> TryFrom<UserDataVariant<T>> for UserDataRef<T> {
#[inline] #[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> { fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
let guard = if !cfg!(feature = "send") || is_sync::<T>() { let guard = if cfg!(not(feature = "send")) || is_sync::<T>() {
variant.raw_lock().try_lock_shared_guarded() variant.raw_lock().try_lock_shared_guarded()
} else { } else {
variant.raw_lock().try_lock_exclusive_guarded() variant.raw_lock().try_lock_exclusive_guarded()
@@ -446,11 +446,11 @@ impl<T> DerefMut for UserDataRefMutInner<T> {
fn try_value_to_userdata<T>(value: Value) -> Result<AnyUserData> { fn try_value_to_userdata<T>(value: Value) -> Result<AnyUserData> {
match value { match value {
Value::UserData(ud) => Ok(ud), Value::UserData(ud) => Ok(ud),
_ => Err(Error::FromLuaConversionError { _ => Err(Error::from_lua_conversion(
from: value.type_name(), value.type_name(),
to: "userdata".to_string(), "userdata",
message: Some(format!("expected userdata of type {}", type_name::<T>())), format!("expected userdata of type {}", type_name::<T>()),
}), )),
} }
} }
+69 -48
View File
@@ -4,15 +4,14 @@ use std::any::TypeId;
use std::cell::RefCell; use std::cell::RefCell;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::os::raw::c_void; use std::os::raw::c_void;
use std::string::String as StdString;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::state::{Lua, LuaGuard}; use crate::state::{Lua, LuaGuard};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti}; use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{Callback, MaybeSend}; use crate::types::{Callback, MaybeSend};
use crate::userdata::{ use crate::userdata::{
borrow_userdata_scoped, borrow_userdata_scoped_mut, AnyUserData, MetaMethod, TypeIdHints, UserData, AnyUserData, MetaMethod, TypeIdHints, UserData, UserDataFields, UserDataMethods, UserDataStorage,
UserDataFields, UserDataMethods, UserDataStorage, borrow_userdata_scoped, borrow_userdata_scoped_mut,
}; };
use crate::util::short_type_name; use crate::util::short_type_name;
use crate::value::Value; use crate::value::Value;
@@ -55,7 +54,10 @@ pub(crate) struct RawUserDataRegistry {
pub(crate) destructor: ffi::lua_CFunction, pub(crate) destructor: ffi::lua_CFunction,
pub(crate) type_id: Option<TypeId>, pub(crate) type_id: Option<TypeId>,
pub(crate) type_name: StdString, pub(crate) type_name: String,
#[cfg(feature = "luau")]
pub(crate) enable_namecall: bool,
} }
impl UserDataType { impl UserDataType {
@@ -100,6 +102,8 @@ impl<T> UserDataRegistry<T> {
destructor: super::util::destroy_userdata_storage::<T>, destructor: super::util::destroy_userdata_storage::<T>,
type_id: r#type.type_id(), type_id: r#type.type_id(),
type_name: short_type_name::<T>(), type_name: short_type_name::<T>(),
#[cfg(feature = "luau")]
enable_namecall: false,
}; };
UserDataRegistry { UserDataRegistry {
@@ -110,6 +114,23 @@ impl<T> UserDataRegistry<T> {
} }
} }
/// Enables support for the namecall optimization in Luau.
///
/// This enables methods resolution optimization in Luau for complex userdata types with methods
/// and field getters. When enabled, Luau will use a faster lookup path for method calls when a
/// specific syntax is used (e.g. `obj:method()`.
///
/// This optimization does not play well with async methods, custom `__index` metamethod and
/// field getters as functions. So, it is disabled by default.
///
/// Use with caution.
#[doc(hidden)]
#[cfg(feature = "luau")]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn enable_namecall(&mut self) {
self.raw.enable_namecall = true;
}
fn box_method<M, A, R>(&self, name: &str, method: M) -> Callback fn box_method<M, A, R>(&self, name: &str, method: M) -> Callback
where where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static, M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
@@ -346,7 +367,7 @@ impl<T> UserDataRegistry<T> {
method: name.to_string(), method: name.to_string(),
type_name: value.type_name(), type_name: value.type_name(),
message: Some("expected nil, table or function".to_string()), message: Some("expected nil, table or function".to_string()),
}) });
} }
} }
} }
@@ -360,106 +381,106 @@ impl<T> UserDataRegistry<T> {
} }
// Returns function name for the type `T`, without the module path // Returns function name for the type `T`, without the module path
fn get_function_name<T>(name: &str) -> StdString { fn get_function_name<T>(name: &str) -> String {
format!("{}.{name}", short_type_name::<T>()) format!("{}.{name}", short_type_name::<T>())
} }
impl<T> UserDataFields<T> for UserDataRegistry<T> { impl<T> UserDataFields<T> for UserDataRegistry<T> {
fn add_field<V>(&mut self, name: impl ToString, value: V) fn add_field<V>(&mut self, name: impl Into<String>, value: V)
where where
V: IntoLua + 'static, V: IntoLua + 'static,
{ {
let name = name.to_string(); let name = name.into();
self.raw.fields.push((name, value.into_lua(self.lua.lua()))); self.raw.fields.push((name, value.into_lua(self.lua.lua())));
} }
fn add_field_method_get<M, R>(&mut self, name: impl ToString, method: M) fn add_field_method_get<M, R>(&mut self, name: impl Into<String>, method: M)
where where
M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static, M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
R: IntoLua, R: IntoLua,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_method(&name, move |lua, data, ()| method(lua, data)); let callback = self.box_method(&name, move |lua, data, ()| method(lua, data));
self.raw.field_getters.push((name, callback)); self.raw.field_getters.push((name, callback));
} }
fn add_field_method_set<M, A>(&mut self, name: impl ToString, method: M) fn add_field_method_set<M, A>(&mut self, name: impl Into<String>, method: M)
where where
M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static, M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
A: FromLua, A: FromLua,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_method_mut(&name, method); let callback = self.box_method_mut(&name, method);
self.raw.field_setters.push((name, callback)); self.raw.field_setters.push((name, callback));
} }
fn add_field_function_get<F, R>(&mut self, name: impl ToString, function: F) fn add_field_function_get<F, R>(&mut self, name: impl Into<String>, function: F)
where where
F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static, F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
R: IntoLua, R: IntoLua,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_function(&name, function); let callback = self.box_function(&name, function);
self.raw.field_getters.push((name, callback)); self.raw.field_getters.push((name, callback));
} }
fn add_field_function_set<F, A>(&mut self, name: impl ToString, mut function: F) fn add_field_function_set<F, A>(&mut self, name: impl Into<String>, mut function: F)
where where
F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static, F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
A: FromLua, A: FromLua,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_function_mut(&name, move |lua, (data, val)| function(lua, data, val)); let callback = self.box_function_mut(&name, move |lua, (data, val)| function(lua, data, val));
self.raw.field_setters.push((name, callback)); self.raw.field_setters.push((name, callback));
} }
fn add_meta_field<V>(&mut self, name: impl ToString, value: V) fn add_meta_field<V>(&mut self, name: impl Into<String>, value: V)
where where
V: IntoLua + 'static, V: IntoLua + 'static,
{ {
let lua = self.lua.lua(); let lua = self.lua.lua();
let name = name.to_string(); let name = name.into();
let field = Self::check_meta_field(lua, &name, value).and_then(|v| v.into_lua(lua)); let field = Self::check_meta_field(lua, &name, value).and_then(|v| v.into_lua(lua));
self.raw.meta_fields.push((name, field)); self.raw.meta_fields.push((name, field));
} }
fn add_meta_field_with<F, R>(&mut self, name: impl ToString, f: F) fn add_meta_field_with<F, R>(&mut self, name: impl Into<String>, f: F)
where where
F: FnOnce(&Lua) -> Result<R> + 'static, F: FnOnce(&Lua) -> Result<R> + 'static,
R: IntoLua, R: IntoLua,
{ {
let lua = self.lua.lua(); let lua = self.lua.lua();
let name = name.to_string(); let name = name.into();
let field = f(lua).and_then(|v| Self::check_meta_field(lua, &name, v).and_then(|v| v.into_lua(lua))); let field = f(lua).and_then(|v| Self::check_meta_field(lua, &name, v).and_then(|v| v.into_lua(lua)));
self.raw.meta_fields.push((name, field)); self.raw.meta_fields.push((name, field));
} }
} }
impl<T> UserDataMethods<T> for UserDataRegistry<T> { impl<T> UserDataMethods<T> for UserDataRegistry<T> {
fn add_method<M, A, R>(&mut self, name: impl ToString, method: M) fn add_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
where where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static, M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
R: IntoLuaMulti, R: IntoLuaMulti,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_method(&name, method); let callback = self.box_method(&name, method);
self.raw.methods.push((name, callback)); self.raw.methods.push((name, callback));
} }
fn add_method_mut<M, A, R>(&mut self, name: impl ToString, method: M) fn add_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
where where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static, M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
R: IntoLuaMulti, R: IntoLuaMulti,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_method_mut(&name, method); let callback = self.box_method_mut(&name, method);
self.raw.methods.push((name, callback)); self.raw.methods.push((name, callback));
} }
#[cfg(feature = "async")] #[cfg(feature = "async")]
fn add_async_method<M, A, MR, R>(&mut self, name: impl ToString, method: M) fn add_async_method<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where where
T: 'static, T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static, M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
@@ -467,13 +488,13 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
MR: Future<Output = Result<R>> + MaybeSend + 'static, MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti, R: IntoLuaMulti,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_async_method(&name, method); let callback = self.box_async_method(&name, method);
self.raw.async_methods.push((name, callback)); self.raw.async_methods.push((name, callback));
} }
#[cfg(feature = "async")] #[cfg(feature = "async")]
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M) fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where where
T: 'static, T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static, M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
@@ -481,70 +502,70 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
MR: Future<Output = Result<R>> + MaybeSend + 'static, MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti, R: IntoLuaMulti,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_async_method_mut(&name, method); let callback = self.box_async_method_mut(&name, method);
self.raw.async_methods.push((name, callback)); self.raw.async_methods.push((name, callback));
} }
fn add_function<F, A, R>(&mut self, name: impl ToString, function: F) fn add_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
where where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static, F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
R: IntoLuaMulti, R: IntoLuaMulti,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_function(&name, function); let callback = self.box_function(&name, function);
self.raw.methods.push((name, callback)); self.raw.methods.push((name, callback));
} }
fn add_function_mut<F, A, R>(&mut self, name: impl ToString, function: F) fn add_function_mut<F, A, R>(&mut self, name: impl Into<String>, function: F)
where where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static, F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
R: IntoLuaMulti, R: IntoLuaMulti,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_function_mut(&name, function); let callback = self.box_function_mut(&name, function);
self.raw.methods.push((name, callback)); self.raw.methods.push((name, callback));
} }
#[cfg(feature = "async")] #[cfg(feature = "async")]
fn add_async_function<F, A, FR, R>(&mut self, name: impl ToString, function: F) fn add_async_function<F, A, FR, R>(&mut self, name: impl Into<String>, function: F)
where where
F: Fn(Lua, A) -> FR + MaybeSend + 'static, F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
FR: Future<Output = Result<R>> + MaybeSend + 'static, FR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti, R: IntoLuaMulti,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_async_function(&name, function); let callback = self.box_async_function(&name, function);
self.raw.async_methods.push((name, callback)); self.raw.async_methods.push((name, callback));
} }
fn add_meta_method<M, A, R>(&mut self, name: impl ToString, method: M) fn add_meta_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
where where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static, M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
R: IntoLuaMulti, R: IntoLuaMulti,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_method(&name, method); let callback = self.box_method(&name, method);
self.raw.meta_methods.push((name, callback)); self.raw.meta_methods.push((name, callback));
} }
fn add_meta_method_mut<M, A, R>(&mut self, name: impl ToString, method: M) fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
where where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static, M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
R: IntoLuaMulti, R: IntoLuaMulti,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_method_mut(&name, method); let callback = self.box_method_mut(&name, method);
self.raw.meta_methods.push((name, callback)); self.raw.meta_methods.push((name, callback));
} }
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl ToString, method: M) fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where where
T: 'static, T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static, M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
@@ -552,13 +573,13 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
MR: Future<Output = Result<R>> + MaybeSend + 'static, MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti, R: IntoLuaMulti,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_async_method(&name, method); let callback = self.box_async_method(&name, method);
self.raw.async_meta_methods.push((name, callback)); self.raw.async_meta_methods.push((name, callback));
} }
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl ToString, method: M) fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where where
T: 'static, T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static, M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
@@ -566,42 +587,42 @@ impl<T> UserDataMethods<T> for UserDataRegistry<T> {
MR: Future<Output = Result<R>> + MaybeSend + 'static, MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti, R: IntoLuaMulti,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_async_method_mut(&name, method); let callback = self.box_async_method_mut(&name, method);
self.raw.async_meta_methods.push((name, callback)); self.raw.async_meta_methods.push((name, callback));
} }
fn add_meta_function<F, A, R>(&mut self, name: impl ToString, function: F) fn add_meta_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
where where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static, F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
R: IntoLuaMulti, R: IntoLuaMulti,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_function(&name, function); let callback = self.box_function(&name, function);
self.raw.meta_methods.push((name, callback)); self.raw.meta_methods.push((name, callback));
} }
fn add_meta_function_mut<F, A, R>(&mut self, name: impl ToString, function: F) fn add_meta_function_mut<F, A, R>(&mut self, name: impl Into<String>, function: F)
where where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static, F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
R: IntoLuaMulti, R: IntoLuaMulti,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_function_mut(&name, function); let callback = self.box_function_mut(&name, function);
self.raw.meta_methods.push((name, callback)); self.raw.meta_methods.push((name, callback));
} }
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))] #[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl ToString, function: F) fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl Into<String>, function: F)
where where
F: Fn(Lua, A) -> FR + MaybeSend + 'static, F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti, A: FromLuaMulti,
FR: Future<Output = Result<R>> + MaybeSend + 'static, FR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti, R: IntoLuaMulti,
{ {
let name = name.to_string(); let name = name.into();
let callback = self.box_async_function(&name, function); let callback = self.box_async_function(&name, function);
self.raw.async_meta_methods.push((name, callback)); self.raw.async_meta_methods.push((name, callback));
} }
+46 -4
View File
@@ -4,8 +4,11 @@ use std::marker::PhantomData;
use std::os::raw::c_int; use std::os::raw::c_int;
use std::ptr; use std::ptr;
use rustc_hash::FxHashMap;
use super::UserDataStorage; use super::UserDataStorage;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::types::CallbackPtr;
use crate::util::{get_userdata, rawget_field, rawset_field, take_userdata}; use crate::util::{get_userdata, rawget_field, rawset_field, take_userdata};
// This is a trick to check if a type is `Sync` or not. // This is a trick to check if a type is `Sync` or not.
@@ -244,6 +247,7 @@ pub(crate) unsafe fn init_userdata_metatable(
field_getters: Option<c_int>, field_getters: Option<c_int>,
field_setters: Option<c_int>, field_setters: Option<c_int>,
methods: Option<c_int>, methods: Option<c_int>,
_methods_map: Option<FxHashMap<Vec<u8>, CallbackPtr>>, // Used only in Luau for `__namecall`
) -> Result<()> { ) -> Result<()> {
if field_getters.is_some() || methods.is_some() { if field_getters.is_some() || methods.is_some() {
// Push `__index` generator function // Push `__index` generator function
@@ -267,6 +271,13 @@ pub(crate) unsafe fn init_userdata_metatable(
} }
rawset_field(state, metatable, "__index")?; rawset_field(state, metatable, "__index")?;
#[cfg(feature = "luau")]
if let Some(methods_map) = _methods_map {
// In Luau we can speedup method calls by providing a dedicated `__namecall` metamethod
push_userdata_metatable_namecall(state, methods_map)?;
rawset_field(state, metatable, "__namecall")?;
}
} }
if let Some(field_setters) = field_setters { if let Some(field_setters) = field_setters {
@@ -354,7 +365,7 @@ unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()
end end
"#; "#;
protect_lua!(state, 0, 1, |state| { protect_lua!(state, 0, 1, |state| {
let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_index")); let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("=__mlua_index"));
if ret != ffi::LUA_OK { if ret != ffi::LUA_OK {
ffi::lua_error(state); ffi::lua_error(state);
} }
@@ -405,7 +416,8 @@ unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result
end end
"#; "#;
protect_lua!(state, 0, 1, |state| { protect_lua!(state, 0, 1, |state| {
let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("__mlua_newindex")); let code_len = code.count_bytes();
let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code_len, cstr!("=__mlua_newindex"));
if ret != ffi::LUA_OK { if ret != ffi::LUA_OK {
ffi::lua_error(state); ffi::lua_error(state);
} }
@@ -424,6 +436,36 @@ unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result
}) })
} }
#[cfg(feature = "luau")]
unsafe fn push_userdata_metatable_namecall(
state: *mut ffi::lua_State,
methods_map: FxHashMap<Vec<u8>, CallbackPtr>,
) -> Result<()> {
unsafe extern "C-unwind" fn namecall(state: *mut ffi::lua_State) -> c_int {
let name = ffi::lua_namecallatom(state, ptr::null_mut());
if name.is_null() {
ffi::luaL_error(state, cstr!("attempt to call an unknown method"));
}
let name_cs = std::ffi::CStr::from_ptr(name);
let methods_map = get_userdata::<FxHashMap<Vec<u8>, CallbackPtr>>(state, ffi::lua_upvalueindex(1));
let callback_ptr = match (*methods_map).get(name_cs.to_bytes()) {
Some(ptr) => *ptr,
#[rustfmt::skip]
None => ffi::luaL_error(state, cstr!("attempt to call an unknown method '%s'"), name),
};
crate::state::callback_error_ext(state, ptr::null_mut(), true, |extra, nargs| {
let rawlua = (*extra).raw_lua();
(*callback_ptr)(rawlua, nargs)
})
}
// Automatic destructor is provided for any Luau userdata
crate::util::push_userdata(state, methods_map, true)?;
protect_lua!(state, 1, 1, |state| {
ffi::lua_pushcclosured(state, namecall, cstr!("__namecall"), 1);
})
}
// This method is called by Lua GC when it's time to collect the userdata. // This method is called by Lua GC when it's time to collect the userdata.
// //
// This method is usually used to collect internal userdata. // This method is usually used to collect internal userdata.
@@ -454,9 +496,9 @@ pub(crate) unsafe extern "C" fn collect_userdata<T>(
// It checks if the userdata is safe to destroy and sets the "destroyed" metatable // It checks if the userdata is safe to destroy and sets the "destroyed" metatable
// to prevent further GC collection. // to prevent further GC collection.
pub(super) unsafe extern "C-unwind" fn destroy_userdata_storage<T>(state: *mut ffi::lua_State) -> c_int { pub(super) unsafe extern "C-unwind" fn destroy_userdata_storage<T>(state: *mut ffi::lua_State) -> c_int {
let ud = get_userdata::<UserDataStorage<T>>(state, -1); let ud = get_userdata::<UserDataStorage<T>>(state, 1);
if (*ud).is_safe_to_destroy() { if (*ud).is_safe_to_destroy() {
take_userdata::<UserDataStorage<T>>(state); take_userdata::<UserDataStorage<T>>(state, 1);
ffi::lua_pushboolean(state, 1); ffi::lua_pushboolean(state, 1);
} else { } else {
ffi::lua_pushboolean(state, 0); ffi::lua_pushboolean(state, 0);
+27 -15
View File
@@ -2,15 +2,15 @@ use std::any::Any;
use std::fmt::Write as _; use std::fmt::Write as _;
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe}; use std::panic::{AssertUnwindSafe, catch_unwind, resume_unwind};
use std::ptr; use std::ptr;
use std::sync::Arc; use std::sync::Arc;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::memory::MemoryState; use crate::memory::MemoryState;
use crate::util::{ use crate::util::{
check_stack, get_internal_userdata, init_internal_metatable, push_internal_userdata, push_string, DESTRUCTED_USERDATA_METATABLE, TypeKey, check_stack, get_internal_userdata, init_internal_metatable,
push_table, rawset_field, to_string, TypeKey, DESTRUCTED_USERDATA_METATABLE, push_internal_userdata, push_string, push_table, rawset_field, to_string,
}; };
static WRAPPED_FAILURE_TYPE_KEY: u8 = 0; static WRAPPED_FAILURE_TYPE_KEY: u8 = 0;
@@ -208,7 +208,7 @@ where
F: FnOnce(*mut ffi::lua_State) -> R, F: FnOnce(*mut ffi::lua_State) -> R,
R: Copy, R: Copy,
{ {
let params = ffi::lua_touserdata(state, -1) as *mut Params<F, R>; let params = ffi::lua_tolightuserdata(state, -1) as *mut Params<F, R>;
ffi::lua_pop(state, 1); ffi::lua_pop(state, 1);
let f = (*params).function.take().unwrap(); let f = (*params).function.take().unwrap();
@@ -239,7 +239,7 @@ where
ffi::lua_pushlightuserdata(state, &mut params as *mut Params<F, R> as *mut c_void); ffi::lua_pushlightuserdata(state, &mut params as *mut Params<F, R> as *mut c_void);
let ret = ffi::lua_pcall(state, nargs + 1, nresults, stack_start + 1); let ret = ffi::lua_pcall(state, nargs + 1, nresults, stack_start + 1);
ffi::lua_remove(state, stack_start + 1); ffi::lua_remove(state, stack_start + 1); // remove error handler
if ret == ffi::LUA_OK { if ret == ffi::LUA_OK {
// `LUA_OK` is only returned when the `do_call` function has completed successfully, so // `LUA_OK` is only returned when the `do_call` function has completed successfully, so
@@ -349,7 +349,11 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<(
state, state,
Some(|state| { Some(|state| {
ffi::lua_pushcfunction(state, error_tostring); ffi::lua_pushcfunction(state, error_tostring);
rawset_field(state, -2, "__tostring") ffi::lua_setfield(state, -2, cstr!("__tostring"));
// This is mostly for Luau typeof() function
ffi::lua_pushstring(state, cstr!("error"));
ffi::lua_setfield(state, -2, cstr!("__type"));
}), }),
)?; )?;
@@ -369,19 +373,19 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<(
"__mod", "__mod",
"__pow", "__pow",
"__unm", "__unm",
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "luau"))]
"__idiv", "__idiv",
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
"__band", "__band",
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
"__bor", "__bor",
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
"__bxor", "__bxor",
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
"__bnot", "__bnot",
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
"__shl", "__shl",
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
"__shr", "__shr",
"__concat", "__concat",
"__len", "__len",
@@ -392,13 +396,21 @@ pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<(
"__newindex", "__newindex",
"__call", "__call",
"__tostring", "__tostring",
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))] #[cfg(any(
feature = "lua55",
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luajit52"
))]
"__pairs", "__pairs",
#[cfg(any(feature = "lua53", feature = "lua52", feature = "luajit52"))] #[cfg(any(feature = "lua53", feature = "lua52", feature = "luajit52"))]
"__ipairs", "__ipairs",
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
"__iter", "__iter",
#[cfg(feature = "lua54")] #[cfg(feature = "luau")]
"__namecall",
#[cfg(any(feature = "lua55", feature = "lua54"))]
"__close", "__close",
] { ] {
ffi::lua_pushvalue(state, -1); ffi::lua_pushvalue(state, -1);
+48 -20
View File
@@ -6,15 +6,16 @@ use std::{ptr, slice, str};
use crate::error::{Error, Result}; use crate::error::{Error, Result};
pub(crate) use error::{ pub(crate) use error::{
error_traceback, error_traceback_thread, init_error_registry, pop_error, protect_lua_call, WrappedFailure, error_traceback, error_traceback_thread, init_error_registry, pop_error,
protect_lua_closure, WrappedFailure, protect_lua_call, protect_lua_closure,
}; };
pub(crate) use path::parse_path as parse_lookup_path;
pub(crate) use short_names::short_type_name; pub(crate) use short_names::short_type_name;
pub(crate) use types::TypeKey; pub(crate) use types::TypeKey;
pub(crate) use userdata::{ pub(crate) use userdata::{
get_destructed_userdata_metatable, get_internal_metatable, get_internal_userdata, get_userdata, DESTRUCTED_USERDATA_METATABLE, get_destructed_userdata_metatable, get_internal_metatable,
init_internal_metatable, push_internal_userdata, push_userdata, take_userdata, get_internal_userdata, get_userdata, init_internal_metatable, push_internal_userdata, push_userdata,
DESTRUCTED_USERDATA_METATABLE, take_userdata,
}; };
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
@@ -88,7 +89,7 @@ impl Drop for StackGuard {
#[inline(always)] #[inline(always)]
pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) -> Result<()> { pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) -> Result<()> {
// Always use protected mode if the string is too long // Always use protected mode if the string is too long
if protect || s.len() > (1 << 30) { if protect || s.len() >= const { 1 << 30 } {
protect_lua!(state, 0, 1, |state| { protect_lua!(state, 0, 1, |state| {
ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len()); ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len());
}) })
@@ -98,18 +99,48 @@ pub(crate) unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect:
} }
} }
// Uses 3 (or 1 if unprotected) stack spaces, does not call checkstack.
#[cfg(feature = "lua55")]
pub(crate) unsafe fn push_external_string(
state: *mut ffi::lua_State,
mut bytes: Vec<u8>,
protect: bool,
) -> Result<()> {
bytes.push(0);
let s_len = bytes.len() - 1; // exclude null terminator
let s_ptr = bytes.as_ptr() as *const c_char;
let bytes_ud = Box::into_raw(Box::new(bytes));
unsafe extern "C" fn dealloc(ud: *mut c_void, _: *mut c_void, _: usize, _: usize) -> *mut c_void {
drop(Box::from_raw(ud as *mut Vec<u8>));
ptr::null_mut()
}
if protect {
let res = protect_lua!(state, 0, 1, move |state| {
ffi::lua_pushexternalstring(state, s_ptr, s_len, Some(dealloc), bytes_ud as *mut _);
});
if res.is_err() {
// Deallocate on error
drop(Box::from_raw(bytes_ud));
return res;
}
} else {
ffi::lua_pushexternalstring(state, s_ptr, s_len, Some(dealloc), bytes_ud as *mut _);
}
Ok(())
}
// Uses 3 stack spaces (when protect), does not call checkstack. // Uses 3 stack spaces (when protect), does not call checkstack.
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
#[inline(always)] #[inline(always)]
pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, b: &[u8], protect: bool) -> Result<()> { pub(crate) unsafe fn push_buffer(state: *mut ffi::lua_State, size: usize, protect: bool) -> Result<*mut u8> {
let data = if protect { let data = if protect || size > const { 1024 * 1024 * 1024 } {
protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, b.len()))? protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, size))?
} else { } else {
ffi::lua_newbuffer(state, b.len()) ffi::lua_newbuffer(state, size)
}; };
let buf = slice::from_raw_parts_mut(data as *mut u8, b.len()); Ok(data as *mut u8)
buf.copy_from_slice(b);
Ok(())
} }
// Uses 3 stack spaces, does not call checkstack. // Uses 3 stack spaces, does not call checkstack.
@@ -122,7 +153,7 @@ pub(crate) unsafe fn push_table(
) -> Result<()> { ) -> Result<()> {
let narr: c_int = narr.try_into().unwrap_or(c_int::MAX); let narr: c_int = narr.try_into().unwrap_or(c_int::MAX);
let nrec: c_int = nrec.try_into().unwrap_or(c_int::MAX); let nrec: c_int = nrec.try_into().unwrap_or(c_int::MAX);
if protect { if protect || narr >= const { 1 << 26 } || nrec >= const { 1 << 26 } {
protect_lua!(state, 0, 1, |state| ffi::lua_createtable(state, narr, nrec)) protect_lua!(state, 0, 1, |state| ffi::lua_createtable(state, narr, nrec))
} else { } else {
ffi::lua_createtable(state, narr, nrec); ffi::lua_createtable(state, narr, nrec);
@@ -221,7 +252,7 @@ pub(crate) unsafe extern "C-unwind" fn safe_xpcall(state: *mut ffi::lua_State) -
// Returns Lua main thread for Lua >= 5.2 or checks that the passed thread is main for Lua 5.1. // Returns Lua main thread for Lua >= 5.2 or checks that the passed thread is main for Lua 5.1.
// Does not call lua_checkstack, uses 1 stack space. // Does not call lua_checkstack, uses 1 stack space.
pub(crate) unsafe fn get_main_state(state: *mut ffi::lua_State) -> Option<*mut ffi::lua_State> { pub(crate) unsafe fn get_main_state(state: *mut ffi::lua_State) -> Option<*mut ffi::lua_State> {
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53", feature = "lua52"))]
{ {
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_MAINTHREAD); ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_MAINTHREAD);
let main_state = ffi::lua_tothread(state, -1); let main_state = ffi::lua_tothread(state, -1);
@@ -233,11 +264,7 @@ pub(crate) unsafe fn get_main_state(state: *mut ffi::lua_State) -> Option<*mut f
// Check the current state first // Check the current state first
let is_main_state = ffi::lua_pushthread(state) == 1; let is_main_state = ffi::lua_pushthread(state) == 1;
ffi::lua_pop(state, 1); ffi::lua_pop(state, 1);
if is_main_state { if is_main_state { Some(state) } else { None }
Some(state)
} else {
None
}
} }
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
Some(ffi::lua_mainthread(state)) Some(ffi::lua_mainthread(state))
@@ -329,6 +356,7 @@ pub(crate) fn linenumber_to_usize(n: c_int) -> Option<usize> {
} }
mod error; mod error;
mod path;
mod short_names; mod short_names;
mod types; mod types;
mod userdata; mod userdata;
+255
View File
@@ -0,0 +1,255 @@
use std::borrow::Cow;
use std::fmt;
use std::iter::Peekable;
use std::str::CharIndices;
use crate::error::{Error, Result};
use crate::state::Lua;
use crate::traits::IntoLua;
use crate::types::Integer;
use crate::value::Value;
#[derive(Debug)]
pub(crate) enum PathKey<'a> {
Str(Cow<'a, str>),
Int(Integer),
}
impl fmt::Display for PathKey<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PathKey::Str(s) => write!(f, "{}", s),
PathKey::Int(i) => write!(f, "{}", i),
}
}
}
impl IntoLua for PathKey<'_> {
fn into_lua(self, lua: &Lua) -> Result<Value> {
match self {
PathKey::Str(s) => Ok(Value::String(lua.create_string(s.as_ref())?)),
PathKey::Int(i) => Ok(Value::Integer(i)),
}
}
}
// Parses a path like `a.b[3]?.c["d"]` into segments of `(key, safe_nil)`.
pub(crate) fn parse_path<'a>(path: &'a str) -> Result<Vec<(PathKey<'a>, bool)>> {
fn read_ident<'a>(path: &'a str, chars: &mut Peekable<CharIndices<'a>>) -> (Cow<'a, str>, bool) {
let mut safe_nil = false;
let start = chars.peek().map(|&(i, _)| i).unwrap_or(path.len());
let mut end = start;
while let Some(&(pos, c)) = chars.peek() {
if c == '.' || c == '?' || c.is_ascii_whitespace() || c == '[' {
if c == '?' {
safe_nil = true;
chars.next(); // consume '?'
}
break;
}
end = pos + c.len_utf8();
chars.next();
}
(Cow::Borrowed(&path[start..end]), safe_nil)
}
let mut segments = Vec::new();
let mut chars = path.char_indices().peekable();
while let Some(&(pos, next)) = chars.peek() {
match next {
'.' => {
// Dot notation: identifier
chars.next();
let (key, safe_nil) = read_ident(path, &mut chars);
if key.is_empty() {
return Err(Error::runtime(format!("empty key in path at position {pos}")));
}
segments.push((PathKey::Str(key), safe_nil));
}
'[' => {
// Bracket notation: either integer or quoted string
chars.next();
let key = match chars.peek() {
Some(&(pos, c @ '0'..='9' | c @ '-')) => {
// Integer key
let negative = c == '-';
if negative {
chars.next(); // consume '-'
}
let mut num: Option<Integer> = None;
while let Some(&(_, c @ '0'..='9')) = chars.peek() {
let new_num = num
.unwrap_or(0)
.checked_mul(10)
.and_then(|n| n.checked_add((c as u8 - b'0') as Integer))
.ok_or_else(|| {
Error::runtime(format!("integer overflow in path at position {pos}"))
})?;
num = Some(new_num);
chars.next(); // consume digit
}
match num {
Some(n) if negative => PathKey::Int(-n),
Some(n) => PathKey::Int(n),
None => {
let err = format!("invalid integer in path at position {pos}");
return Err(Error::runtime(err));
}
}
}
Some((_, '\'' | '"')) => {
// Quoted string
PathKey::Str(unquote_string(path, &mut chars)?)
}
Some((_, ']')) => {
return Err(Error::runtime(format!("empty key in path at position {pos}")));
}
Some((pos, c)) => {
let err = format!("unexpected character '{c}' in path at position {pos}");
return Err(Error::runtime(err));
}
None => {
return Err(Error::runtime("unexpected end of path"));
}
};
// Expect closing bracket
let mut safe_nil = false;
match chars.next() {
Some((_, ']')) => {
// Check for optional safe-nil operator
if let Some(&(_, '?')) = chars.peek() {
safe_nil = true;
chars.next(); // consume '?'
}
}
Some((pos, c)) => {
let err = format!("expected ']' in path at position {pos}, found '{c}'");
return Err(Error::runtime(err));
}
None => {
return Err(Error::runtime("unexpected end of path"));
}
}
segments.push((key, safe_nil));
}
c if c.is_ascii_whitespace() => {
chars.next(); // Skip whitespace
}
_ if segments.is_empty() => {
// First segment without dot/bracket notation
let (key_cow, safe_nil) = read_ident(path, &mut chars);
if key_cow.is_empty() {
return Err(Error::runtime(format!("empty key in path at position {pos}")));
}
segments.push((PathKey::Str(key_cow), safe_nil));
}
c => {
let err = format!("unexpected character '{c}' in path at position {pos}");
return Err(Error::runtime(err));
}
}
}
Ok(segments)
}
fn unquote_string<'a>(path: &'a str, chars: &mut Peekable<CharIndices<'a>>) -> Result<Cow<'a, str>> {
let (start_pos, first_quote) = chars.next().unwrap();
let mut result = String::new();
loop {
match chars.next() {
Some((pos, '\\')) => {
if result.is_empty() {
// First escape found, copy everything up to this point
result.push_str(&path[start_pos + 1..pos]);
}
match chars.next() {
Some((_, '\\')) => result.push('\\'),
Some((_, '"')) => result.push('"'),
Some((_, '\'')) => result.push('\''),
Some((_, other)) => {
result.push('\\');
result.push(other);
}
None => continue, // will be handled by outer loop
}
}
Some((pos, c)) if c == first_quote => {
if !result.is_empty() {
return Ok(Cow::Owned(result));
}
// No escapes, return borrowed slice
return Ok(Cow::Borrowed(&path[start_pos + 1..pos]));
}
Some((_, c)) => {
if !result.is_empty() {
result.push(c);
}
// If no escapes yet, continue tracking for potential borrowed slice
}
None => {
let err = format!("unexpected end of string at position {start_pos}");
return Err(Error::runtime(err));
}
}
}
}
#[cfg(test)]
mod tests {
use super::{PathKey, parse_path};
#[test]
fn test_parse_path() {
// Test valid paths
let path = parse_path("a.b[3]?.c['d']").unwrap();
assert_eq!(path.len(), 5);
assert!(matches!(path[0], (PathKey::Str(ref s), false) if s == "a"));
assert!(matches!(path[1], (PathKey::Str(ref s), false) if s == "b"));
assert!(matches!(path[2], (PathKey::Int(3), true)));
assert!(matches!(path[3], (PathKey::Str(ref s), false) if s == "c"));
assert!(matches!(path[4], (PathKey::Str(ref s), false) if s == "d"));
// Test empty path
let path = parse_path("").unwrap();
assert_eq!(path.len(), 0);
let path = parse_path(" ").unwrap();
assert_eq!(path.len(), 0);
// Test invalid dot syntax
let err = parse_path("a..b").unwrap_err().to_string();
assert_eq!(err, "runtime error: empty key in path at position 1");
let err = parse_path("a.b.").unwrap_err().to_string();
assert_eq!(err, "runtime error: empty key in path at position 3");
// Test invalid bracket syntax
let err = parse_path("a[unclosed").unwrap_err().to_string();
assert_eq!(
err,
"runtime error: unexpected character 'u' in path at position 2"
);
let err = parse_path("a[]").unwrap_err().to_string();
assert_eq!(err, "runtime error: empty key in path at position 1");
let err = parse_path(r#"a["unclosed"#).unwrap_err().to_string();
assert_eq!(err, "runtime error: unexpected end of string at position 2");
let err = parse_path(r#"a["#).unwrap_err().to_string();
assert_eq!(err, "runtime error: unexpected end of path");
let err = parse_path(r#"a[123"#).unwrap_err().to_string();
assert_eq!(err, "runtime error: unexpected end of path");
let err = parse_path(r#"a['bla'123"#).unwrap_err().to_string();
assert_eq!(
err,
"runtime error: expected ']' in path at position 7, found '1'"
);
let err = parse_path(r#"a["bla"]x"#).unwrap_err().to_string();
assert_eq!(
err,
"runtime error: unexpected character 'x' in path at position 8"
);
// Test bad integers
let err = parse_path("a[99999999999999999999]").unwrap_err().to_string();
assert_eq!(err, "runtime error: integer overflow in path at position 2");
let err = parse_path("a[-]").unwrap_err().to_string();
assert_eq!(err, "runtime error: invalid integer in path at position 2");
}
}
+18 -8
View File
@@ -1,6 +1,6 @@
//! Mostly copied from [bevy_utils] //! Inspired by bevy's [disqualified]
//! //!
//! [bevy_utils]: https://github.com/bevyengine/bevy/blob/main/crates/bevy_utils/src/short_names.rs //! [disqualified]: https://github.com/bevyengine/disqualified/blob/main/src/short_name.rs
use std::any::type_name; use std::any::type_name;
@@ -23,8 +23,7 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
while index < end_of_string { while index < end_of_string {
let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default(); let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default();
// Collapse everything up to the next special character, // Collapse everything up to the next special character, then skip over it
// then skip over it
if let Some(special_character_index) = if let Some(special_character_index) =
rest_of_string.find(|c: char| [' ', '<', '>', '(', ')', '[', ']', ',', ';'].contains(&c)) rest_of_string.find(|c: char| [' ', '<', '>', '(', ')', '[', ']', ',', ';'].contains(&c))
{ {
@@ -32,11 +31,16 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
parsed_name += collapse_type_name(segment_to_collapse); parsed_name += collapse_type_name(segment_to_collapse);
// Insert the special character // Insert the special character
let special_character = &rest_of_string[special_character_index..=special_character_index]; let special_character = &rest_of_string[special_character_index..=special_character_index];
parsed_name.push_str(special_character); parsed_name += special_character;
// Remove lifetimes like <'_> or <'_, '_, ...>
if parsed_name.ends_with("<'_>") || parsed_name.ends_with("<'_, ") {
_ = parsed_name.split_off(parsed_name.len() - 4);
}
match special_character { match special_character {
">" | ")" | "]" if rest_of_string[special_character_index + 1..].starts_with("::") => { ">" | ")" | "]" if rest_of_string[special_character_index + 1..].starts_with("::") => {
parsed_name.push_str("::"); parsed_name += "::";
// Move the index past the "::" // Move the index past the "::"
index += special_character_index + 3; index += special_character_index + 3;
} }
@@ -53,14 +57,18 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
} }
#[inline(always)] #[inline(always)]
fn collapse_type_name(string: &str) -> &str { fn collapse_type_name(segment: &str) -> &str {
string.rsplit("::").next().unwrap() segment.rsplit("::").next().unwrap()
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::short_type_name; use super::short_type_name;
use std::collections::HashMap; use std::collections::HashMap;
use std::marker::PhantomData;
struct MyData<'a, 'b>(PhantomData<&'a &'b ()>);
struct MyDataT<'a, T>(PhantomData<&'a T>);
#[test] #[test]
fn tests() { fn tests() {
@@ -73,5 +81,7 @@ mod tests {
"HashMap<String, Option<[i32; 3]>>" "HashMap<String, Option<[i32; 3]>>"
); );
assert_eq!(short_type_name::<dyn Fn(i32) -> i32>(), "dyn Fn(i32) -> i32"); assert_eq!(short_type_name::<dyn Fn(i32) -> i32>(), "dyn Fn(i32) -> i32");
assert_eq!(short_type_name::<MyDataT<&str>>(), "MyDataT<&str>");
assert_eq!(short_type_name::<(&MyData, [MyData])>(), "(MyData, [MyData])");
} }
} }
+23 -20
View File
@@ -3,7 +3,7 @@ use std::{mem, ptr};
use crate::error::Result; use crate::error::Result;
use crate::userdata::collect_userdata; use crate::userdata::collect_userdata;
use crate::util::{check_stack, get_metatable_ptr, push_table, rawset_field, TypeKey}; use crate::util::{TypeKey, check_stack, get_metatable_ptr, push_table, rawset_field};
// Pushes the userdata and attaches a metatable with __gc method. // Pushes the userdata and attaches a metatable with __gc method.
// Internally uses 3 stack spaces, does not call checkstack. // Internally uses 3 stack spaces, does not call checkstack.
@@ -47,7 +47,7 @@ pub(crate) unsafe fn get_internal_metatable<T: TypeKey>(state: *mut ffi::lua_Sta
// Uses 6 stack spaces and calls checkstack. // Uses 6 stack spaces and calls checkstack.
pub(crate) unsafe fn init_internal_metatable<T: TypeKey>( pub(crate) unsafe fn init_internal_metatable<T: TypeKey>(
state: *mut ffi::lua_State, state: *mut ffi::lua_State,
customize_fn: Option<fn(*mut ffi::lua_State) -> Result<()>>, customize_fn: Option<fn(*mut ffi::lua_State)>,
) -> Result<()> { ) -> Result<()> {
check_stack(state, 6)?; check_stack(state, 6)?;
@@ -62,18 +62,18 @@ pub(crate) unsafe fn init_internal_metatable<T: TypeKey>(
ffi::lua_pushboolean(state, 0); ffi::lua_pushboolean(state, 0);
rawset_field(state, -2, "__metatable")?; rawset_field(state, -2, "__metatable")?;
if let Some(f) = customize_fn {
f(state)?;
}
protect_lua!(state, 1, 0, |state| { protect_lua!(state, 1, 0, |state| {
if let Some(f) = customize_fn {
f(state);
}
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, T::type_key()); ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, T::type_key());
})?; })?;
Ok(()) Ok(())
} }
// Uses 2 stack spaces, does not call checkstack // Uses up to 1 stack space, does not call `checkstack`
pub(crate) unsafe fn get_internal_userdata<T: TypeKey>( pub(crate) unsafe fn get_internal_userdata<T: TypeKey>(
state: *mut ffi::lua_State, state: *mut ffi::lua_State,
index: c_int, index: c_int,
@@ -141,24 +141,27 @@ pub(crate) unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -
ud ud
} }
// Pops the userdata off of the top of the stack and returns it to rust, invalidating the lua /// Unwraps `T` from the Lua userdata and invalidating it by setting the special "destructed"
// userdata and gives it the special "destructed" userdata metatable. Userdata must not have been /// metatable.
// previously invalidated, and this method does not check for this. ///
// Uses 1 extra stack space and does not call checkstack. /// This method does not check that userdata is of type `T` and was not previously invalidated.
pub(crate) unsafe fn take_userdata<T>(state: *mut ffi::lua_State) -> T { ///
// We set the metatable of userdata on __gc to a special table with no __gc method and with /// Uses 1 extra stack space, does not call checkstack.
// metamethods that trigger an error on access. We do this so that it will not be double pub(crate) unsafe fn take_userdata<T>(state: *mut ffi::lua_State, idx: c_int) -> T {
// dropped, and also so that it cannot be used or identified as any particular userdata type #[rustfmt::skip]
// after the first call to __gc. let idx = if idx < 0 { ffi::lua_absindex(state, idx) } else { idx };
// Update the metatable of this userdata to a special one with no `__gc` method and with
// metamethods that trigger an error on access.
// We do this so that it will not be double dropped or used after being dropped.
get_destructed_userdata_metatable(state); get_destructed_userdata_metatable(state);
ffi::lua_setmetatable(state, -2); ffi::lua_setmetatable(state, idx);
let ud = get_userdata::<T>(state, -1); let ud = get_userdata::<T>(state, idx);
// Update userdata tag to disable destructor and mark as destructed // Update userdata tag to disable destructor and mark as destructed
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
ffi::lua_setuserdatatag(state, -1, 1); ffi::lua_setuserdatatag(state, idx, 1);
ffi::lua_pop(state, 1);
ptr::read(ud) ptr::read(ud)
} }
+50 -38
View File
@@ -1,21 +1,20 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::HashSet; use std::collections::HashSet;
use std::os::raw::c_void; use std::os::raw::c_void;
use std::string::String as StdString;
use std::{fmt, ptr, str}; use std::{fmt, ptr, str};
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::function::Function; use crate::function::Function;
use crate::string::{BorrowedStr, String}; use crate::string::{BorrowedStr, LuaString};
use crate::table::Table; use crate::table::Table;
use crate::thread::Thread; use crate::thread::Thread;
use crate::types::{Integer, LightUserData, Number, ValueRef}; use crate::types::{Integer, LightUserData, Number, ValueRef};
use crate::userdata::AnyUserData; use crate::userdata::AnyUserData;
use crate::util::{check_stack, StackGuard}; use crate::util::{StackGuard, check_stack};
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
use { use {
crate::table::SerializableTable, crate::table::SerializableTable,
rustc_hash::FxHashSet, rustc_hash::FxHashSet,
@@ -28,9 +27,10 @@ use {
/// The non-primitive variants (eg. string/table/function/thread/userdata) contain handle types /// The non-primitive variants (eg. string/table/function/thread/userdata) contain handle types
/// into the internal Lua state. It is a logic error to mix handle types between separate /// into the internal Lua state. It is a logic error to mix handle types between separate
/// `Lua` instances, and doing so will result in a panic. /// `Lua` instances, and doing so will result in a panic.
#[derive(Clone)] #[derive(Clone, Default)]
pub enum Value { pub enum Value {
/// The Lua value `nil`. /// The Lua value `nil`.
#[default]
Nil, Nil,
/// The Lua value `true` or `false`. /// The Lua value `true` or `false`.
Boolean(bool), Boolean(bool),
@@ -49,7 +49,7 @@ pub enum Value {
/// An interned string, managed by Lua. /// An interned string, managed by Lua.
/// ///
/// Unlike Rust strings, Lua strings may not be valid UTF-8. /// Unlike Rust strings, Lua strings may not be valid UTF-8.
String(String), String(LuaString),
/// Reference to a Lua table. /// Reference to a Lua table.
Table(Table), Table(Table),
/// Reference to a Lua function (or closure). /// Reference to a Lua function (or closure).
@@ -128,7 +128,7 @@ impl Value {
#[inline] #[inline]
pub fn to_pointer(&self) -> *const c_void { pub fn to_pointer(&self) -> *const c_void {
match self { match self {
Value::String(String(vref)) => { Value::String(LuaString(vref)) => {
// In Lua < 5.4 (excluding Luau), string pointers are NULL // In Lua < 5.4 (excluding Luau), string pointers are NULL
// Use alternative approach // Use alternative approach
let lua = vref.lua.lock(); let lua = vref.lua.lock();
@@ -150,8 +150,8 @@ impl Value {
/// ///
/// This might invoke the `__tostring` metamethod for non-primitive types (eg. tables, /// This might invoke the `__tostring` metamethod for non-primitive types (eg. tables,
/// functions). /// functions).
pub fn to_string(&self) -> Result<StdString> { pub fn to_string(&self) -> Result<String> {
unsafe fn invoke_to_string(vref: &ValueRef) -> Result<StdString> { unsafe fn invoke_to_string(vref: &ValueRef) -> Result<String> {
let lua = vref.lua.lock(); let lua = vref.lua.lock();
let state = lua.state(); let state = lua.state();
let _guard = StackGuard::new(state); let _guard = StackGuard::new(state);
@@ -161,7 +161,7 @@ impl Value {
protect_lua!(state, 1, 1, fn(state) { protect_lua!(state, 1, 1, fn(state) {
ffi::luaL_tolstring(state, -1, ptr::null_mut()); ffi::luaL_tolstring(state, -1, ptr::null_mut());
})?; })?;
Ok(String(lua.pop_ref()).to_str()?.to_string()) Ok(LuaString(lua.pop_ref()).to_str()?.to_string())
} }
match self { match self {
@@ -335,17 +335,17 @@ impl Value {
self.as_number() self.as_number()
} }
/// Returns `true` if the value is a Lua [`String`]. /// Returns `true` if the value is a [`LuaString`].
#[inline] #[inline]
pub fn is_string(&self) -> bool { pub fn is_string(&self) -> bool {
self.as_string().is_some() self.as_string().is_some()
} }
/// Cast the value to Lua [`String`]. /// Cast the value to a [`LuaString`].
/// ///
/// If the value is a Lua [`String`], returns it or `None` otherwise. /// If the value is a [`LuaString`], returns it or `None` otherwise.
#[inline] #[inline]
pub fn as_string(&self) -> Option<&String> { pub fn as_string(&self) -> Option<&LuaString> {
match self { match self {
Value::String(s) => Some(s), Value::String(s) => Some(s),
_ => None, _ => None,
@@ -354,18 +354,26 @@ impl Value {
/// Cast the value to [`BorrowedStr`]. /// Cast the value to [`BorrowedStr`].
/// ///
/// If the value is a Lua [`String`], try to convert it to [`BorrowedStr`] or return `None` /// If the value is a [`LuaString`], try to convert it to [`BorrowedStr`] or return `None`
/// otherwise. /// otherwise.
#[deprecated(
since = "0.11.0",
note = "This method does not follow Rust naming convention. Use `as_string().and_then(|s| s.to_str().ok())` instead."
)]
#[inline] #[inline]
pub fn as_str(&self) -> Option<BorrowedStr> { pub fn as_str(&self) -> Option<BorrowedStr<'_>> {
self.as_string().and_then(|s| s.to_str().ok()) self.as_string().and_then(|s| s.to_str().ok())
} }
/// Cast the value to [`StdString`]. /// Cast the value to [`String`].
/// ///
/// If the value is a Lua [`String`], converts it to [`StdString`] or returns `None` otherwise. /// If the value is a [`LuaString`], converts it to [`String`] or returns `None` otherwise.
#[deprecated(
since = "0.11.0",
note = "This method does not follow Rust naming convention. Use `as_string().map(|s| s.to_string_lossy())` instead."
)]
#[inline] #[inline]
pub fn as_string_lossy(&self) -> Option<StdString> { pub fn as_string_lossy(&self) -> Option<String> {
self.as_string().map(|s| s.to_string_lossy()) self.as_string().map(|s| s.to_string_lossy())
} }
@@ -481,10 +489,9 @@ impl Value {
/// Wrap reference to this Value into [`SerializableValue`]. /// Wrap reference to this Value into [`SerializableValue`].
/// ///
/// This allows customizing serialization behavior using serde. /// This allows customizing serialization behavior using serde.
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
#[doc(hidden)] pub fn to_serializable(&self) -> SerializableValue<'_> {
pub fn to_serializable(&self) -> SerializableValue {
SerializableValue::new(self, Default::default(), None) SerializableValue::new(self, Default::default(), None)
} }
@@ -572,12 +579,6 @@ impl Value {
} }
} }
impl Default for Value {
fn default() -> Self {
Self::Nil
}
}
impl fmt::Debug for Value { impl fmt::Debug for Value {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if fmt.alternate() { if fmt.alternate() {
@@ -630,8 +631,8 @@ impl PartialEq for Value {
} }
/// A wrapped [`Value`] with customized serialization behavior. /// A wrapped [`Value`] with customized serialization behavior.
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub struct SerializableValue<'a> { pub struct SerializableValue<'a> {
value: &'a Value, value: &'a Value,
options: crate::serde::de::Options, options: crate::serde::de::Options,
@@ -639,7 +640,7 @@ pub struct SerializableValue<'a> {
visited: Option<Rc<RefCell<FxHashSet<*const c_void>>>>, visited: Option<Rc<RefCell<FxHashSet<*const c_void>>>>,
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
impl Serialize for Value { impl Serialize for Value {
#[inline] #[inline]
fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> { fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
@@ -647,7 +648,7 @@ impl Serialize for Value {
} }
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
impl<'a> SerializableValue<'a> { impl<'a> SerializableValue<'a> {
#[inline] #[inline]
pub(crate) fn new( pub(crate) fn new(
@@ -676,7 +677,7 @@ impl<'a> SerializableValue<'a> {
/// ///
/// Default: **true** /// Default: **true**
#[must_use] #[must_use]
pub const fn deny_unsupported_types(mut self, enabled: bool) -> Self { pub fn deny_unsupported_types(mut self, enabled: bool) -> Self {
self.options.deny_unsupported_types = enabled; self.options.deny_unsupported_types = enabled;
self self
} }
@@ -687,7 +688,7 @@ impl<'a> SerializableValue<'a> {
/// ///
/// Default: **true** /// Default: **true**
#[must_use] #[must_use]
pub const fn deny_recursive_tables(mut self, enabled: bool) -> Self { pub fn deny_recursive_tables(mut self, enabled: bool) -> Self {
self.options.deny_recursive_tables = enabled; self.options.deny_recursive_tables = enabled;
self self
} }
@@ -696,7 +697,7 @@ impl<'a> SerializableValue<'a> {
/// ///
/// Default: **false** /// Default: **false**
#[must_use] #[must_use]
pub const fn sort_keys(mut self, enabled: bool) -> Self { pub fn sort_keys(mut self, enabled: bool) -> Self {
self.options.sort_keys = enabled; self.options.sort_keys = enabled;
self self
} }
@@ -705,13 +706,24 @@ impl<'a> SerializableValue<'a> {
/// ///
/// Default: **false** /// Default: **false**
#[must_use] #[must_use]
pub const fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self { pub fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self {
self.options.encode_empty_tables_as_array = enabled; self.options.encode_empty_tables_as_array = enabled;
self self
} }
/// If true, enable detection of mixed tables.
///
/// A mixed table is a table that has both array-like and map-like entries or several borders.
///
/// Default: **false**
#[must_use]
pub fn detect_mixed_tables(mut self, enabled: bool) -> Self {
self.options.detect_mixed_tables = enabled;
self
}
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
impl Serialize for SerializableValue<'_> { impl Serialize for SerializableValue<'_> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where where
+2 -3
View File
@@ -1,6 +1,6 @@
use std::fmt; use std::fmt;
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
use serde::ser::{Serialize, SerializeTupleStruct, Serializer}; use serde::ser::{Serialize, SerializeTupleStruct, Serializer};
/// A Luau vector type. /// A Luau vector type.
@@ -38,7 +38,6 @@ impl Vector {
} }
/// Creates a new vector with all components set to `0.0`. /// Creates a new vector with all components set to `0.0`.
#[doc(hidden)]
pub const fn zero() -> Self { pub const fn zero() -> Self {
Self([0.0; Self::SIZE]) Self([0.0; Self::SIZE])
} }
@@ -66,7 +65,7 @@ impl Vector {
} }
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serde")]
impl Serialize for Vector { impl Serialize for Vector {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> { fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
let mut ts = serializer.serialize_tuple_struct("Vector", Self::SIZE)?; let mut ts = serializer.serialize_tuple_struct("Vector", Self::SIZE)?;
+10 -10
View File
@@ -1,23 +1,23 @@
[lua54] [lua55]
features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers" features = "lua55,vendored,async,send,serde,macros,anyhow,userdata-wrappers"
[lua54_non_send] [lua55_non_send]
features = "lua54,vendored,async,serialize,macros,anyhow,userdata-wrappers" features = "lua55,vendored,async,serde,macros,anyhow,userdata-wrappers"
[lua54_with_memory_limit] [lua55_with_memory_limit]
features = "lua54,vendored,async,send,serialize,macros,anyhow,userdata-wrappers" features = "lua55,vendored,async,send,serde,macros,anyhow,userdata-wrappers"
rustflags = "--cfg force_memory_limit" rustflags = "--cfg force_memory_limit"
[lua51] [lua51]
features = "lua51,vendored,async,send,serialize,macros" features = "lua51,vendored,async,send,serde,macros"
[lua51_with_memory_limit] [lua51_with_memory_limit]
features = "lua51,vendored,async,send,serialize,macros" features = "lua51,vendored,async,send,serde,macros"
rustflags = "--cfg force_memory_limit" rustflags = "--cfg force_memory_limit"
[luau] [luau]
features = "luau,async,send,serialize,macros" features = "luau,async,send,serde,macros"
[luau_with_memory_limit] [luau_with_memory_limit]
features = "luau,async,send,serialize,macros" features = "luau,async,send,serde,macros"
rustflags = "--cfg force_memory_limit" rustflags = "--cfg force_memory_limit"
+104 -11
View File
@@ -1,6 +1,5 @@
#![cfg(feature = "async")] #![cfg(feature = "async")]
use std::string::String as StdString;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
@@ -8,8 +7,8 @@ use futures_util::stream::TryStreamExt;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use mlua::{ use mlua::{
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, UserData, Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, ThreadStatus, UserData,
UserDataMethods, Value, UserDataMethods, UserDataRef, Value,
}; };
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@@ -40,7 +39,7 @@ async fn test_async_function() -> Result<()> {
async fn test_async_function_wrap() -> Result<()> { async fn test_async_function_wrap() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
let f = Function::wrap_async(|s: StdString| async move { let f = Function::wrap_async(|s: String| async move {
tokio::task::yield_now().await; tokio::task::yield_now().await;
Ok(s) Ok(s)
}); });
@@ -68,7 +67,7 @@ async fn test_async_function_wrap() -> Result<()> {
async fn test_async_function_wrap_raw() -> Result<()> { async fn test_async_function_wrap_raw() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
let f = Function::wrap_raw_async(|s: StdString| async move { let f = Function::wrap_raw_async(|s: String| async move {
tokio::task::yield_now().await; tokio::task::yield_now().await;
s s
}); });
@@ -249,7 +248,7 @@ async fn test_async_return_async_closure() -> Result<()> {
Ok(()) Ok(())
} }
#[cfg(feature = "lua54")] #[cfg(any(feature = "lua55", feature = "lua54"))]
#[tokio::test] #[tokio::test]
async fn test_async_lua54_to_be_closed() -> Result<()> { async fn test_async_lua54_to_be_closed() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
@@ -386,7 +385,7 @@ async fn test_async_table_object_like() -> Result<()> {
table.get::<i64>("val") table.get::<i64>("val")
})?, })?,
)?; )?;
table.set_metatable(Some(metatable)); table.set_metatable(Some(metatable))?;
assert_eq!(table.call_async::<i64>(()).await.unwrap(), 15); assert_eq!(table.call_async::<i64>(()).await.unwrap(), 15);
match table.call_async_method::<()>("non_existent", ()).await { match table.call_async_method::<()>("non_existent", ()).await {
@@ -423,9 +422,9 @@ async fn test_async_thread_pool() -> Result<()> {
#[tokio::test] #[tokio::test]
async fn test_async_userdata() -> Result<()> { async fn test_async_userdata() -> Result<()> {
struct MyUserData(u64); struct MyUserdata(u64);
impl UserData for MyUserData { impl UserData for MyUserdata {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_async_method("get_value", |_, data, ()| async move { methods.add_async_method("get_value", |_, data, ()| async move {
sleep_ms(10).await; sleep_ms(10).await;
@@ -438,6 +437,11 @@ async fn test_async_userdata() -> Result<()> {
Ok(()) Ok(())
}); });
methods.add_async_method_once("take_value", |_, data, ()| async move {
sleep_ms(10).await;
Ok(data.0)
});
methods.add_async_function("sleep", |_, n| async move { methods.add_async_function("sleep", |_, n| async move {
sleep_ms(n).await; sleep_ms(n).await;
Ok(format!("elapsed:{}ms", n)) Ok(format!("elapsed:{}ms", n))
@@ -479,7 +483,7 @@ async fn test_async_userdata() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals(); let globals = lua.globals();
let userdata = lua.create_userdata(MyUserData(11))?; let userdata = lua.create_userdata(MyUserdata(11))?;
globals.set("userdata", &userdata)?; globals.set("userdata", &userdata)?;
lua.load( lua.load(
@@ -518,6 +522,21 @@ async fn test_async_userdata() -> Result<()> {
#[cfg(not(any(feature = "lua51", feature = "luau")))] #[cfg(not(any(feature = "lua51", feature = "luau")))]
assert_eq!(userdata.call_async::<String>(()).await?, "elapsed:24ms"); assert_eq!(userdata.call_async::<String>(()).await?, "elapsed:24ms");
// Take value
let userdata2 = lua.create_userdata(MyUserdata(0))?;
globals.set("userdata2", userdata2)?;
lua.load("assert(userdata:take_value() == 24)")
.exec_async()
.await?;
match lua.load("userdata2.take_value(userdata)").exec_async().await {
Err(Error::CallbackError { cause, .. }) => {
let err = cause.to_string();
assert!(err.contains("bad argument `self` to `MyUserdata.take_value`"));
assert!(err.contains("userdata has been destructed"));
}
r => panic!("expected Err(CallbackError), got {r:?}"),
}
Ok(()) Ok(())
} }
@@ -547,6 +566,7 @@ async fn test_async_thread_error() -> Result<()> {
#[tokio::test] #[tokio::test]
async fn test_async_terminate() -> Result<()> { async fn test_async_terminate() -> Result<()> {
// Future captures `Lua` instance and dropped all together
let mutex = Arc::new(Mutex::new(0u32)); let mutex = Arc::new(Mutex::new(0u32));
{ {
let lua = Lua::new(); let lua = Lua::new();
@@ -565,6 +585,17 @@ async fn test_async_terminate() -> Result<()> {
} }
assert!(mutex.try_lock().is_ok()); assert!(mutex.try_lock().is_ok());
// Future is dropped, but `Lua` instance is still alive
let lua = Lua::new();
let func = lua.create_async_function(move |_, mutex: UserDataRef<Arc<Mutex<u32>>>| async move {
let _guard = mutex.lock().await;
sleep_ms(100).await;
Ok(())
})?;
let mutex2 = lua.create_any_userdata(mutex.clone())?;
let _ = tokio::time::timeout(Duration::from_millis(30), func.call_async::<()>(mutex2)).await;
assert!(mutex.try_lock().is_ok());
Ok(()) Ok(())
} }
@@ -598,6 +629,36 @@ async fn test_async_task() -> Result<()> {
Ok(()) Ok(())
} }
#[tokio::test]
async fn test_async_task_abort() -> Result<()> {
let lua = Lua::new();
let sleep = lua.create_async_function(move |_lua, n: u64| async move {
sleep_ms(n).await;
Ok(())
})?;
lua.globals().set("sleep", sleep)?;
let local = tokio::task::LocalSet::new();
local
.run_until(async {
let lua2 = lua.clone();
let jh = tokio::task::spawn_local(async move {
lua2.load("sleep(200) result = 'done'")
.exec_async()
.await
.unwrap();
});
sleep_ms(100).await; // Wait for the task to start
jh.abort();
})
.await;
local.await;
assert_eq!(lua.globals().get::<Value>("result")?, Value::Nil);
Ok(())
}
#[tokio::test] #[tokio::test]
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
async fn test_async_hook() -> Result<()> { async fn test_async_hook() -> Result<()> {
@@ -608,7 +669,7 @@ async fn test_async_hook() -> Result<()> {
static HOOK_CALLED: AtomicBool = AtomicBool::new(false); static HOOK_CALLED: AtomicBool = AtomicBool::new(false);
lua.set_global_hook(mlua::HookTriggers::new().every_line(), move |_, _| { lua.set_global_hook(mlua::HookTriggers::new().every_line(), move |_, _| {
if !HOOK_CALLED.swap(true, Ordering::Relaxed) { if !HOOK_CALLED.swap(true, Ordering::Relaxed) {
#[cfg(any(feature = "lu53", feature = "lua54"))] #[cfg(any(feature = "lua55", feature = "lua54", feature = "lua53"))]
return Ok(mlua::VmState::Yield); return Ok(mlua::VmState::Yield);
} }
Ok(mlua::VmState::Continue) Ok(mlua::VmState::Continue)
@@ -625,3 +686,35 @@ async fn test_async_hook() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_async_yield_with() -> Result<()> {
let lua = Lua::new();
let func = lua.create_async_function(|lua, (mut a, mut b): (i32, i32)| async move {
let zero = lua.yield_with::<MultiValue>(()).await?;
assert!(zero.is_empty());
let one = lua.yield_with::<MultiValue>(a + b).await?;
assert_eq!(one.len(), 1);
for _ in 0..3 {
(a, b) = lua.yield_with((a + b, a * b)).await?;
}
Ok((0, 0))
})?;
let thread = lua.create_thread(func)?;
let zero = thread.resume::<MultiValue>((2, 3))?; // function arguments
assert!(zero.is_empty());
let one = thread.resume::<i32>(())?; // value of "zero" is passed here
assert_eq!(one, 5);
assert_eq!(thread.resume::<(i32, i32)>(1)?, (5, 6)); // value of "one" is passed here
assert_eq!(thread.resume::<(i32, i32)>((10, 11))?, (21, 110));
assert_eq!(thread.resume::<(i32, i32)>((11, 12))?, (23, 132));
assert_eq!(thread.resume::<(i32, i32)>((12, 13))?, (0, 0));
assert_eq!(thread.status(), ThreadStatus::Finished);
Ok(())
}
+68 -2
View File
@@ -1,5 +1,7 @@
#![cfg(feature = "luau")] #![cfg(feature = "luau")]
use std::io::{Read, Seek, SeekFrom, Write};
use mlua::{Lua, Result, Value}; use mlua::{Lua, Result, Value};
#[test] #[test]
@@ -41,7 +43,7 @@ fn test_buffer() -> Result<()> {
} }
#[test] #[test]
#[should_panic(expected = "range end index 14 out of range for slice of length 13")] #[should_panic(expected = "out of range for slice of length 13")]
fn test_buffer_out_of_bounds_read() { fn test_buffer_out_of_bounds_read() {
let lua = Lua::new(); let lua = Lua::new();
let buf = lua.create_buffer(b"hello, world!").unwrap(); let buf = lua.create_buffer(b"hello, world!").unwrap();
@@ -49,9 +51,73 @@ fn test_buffer_out_of_bounds_read() {
} }
#[test] #[test]
#[should_panic(expected = "range end index 16 out of range for slice of length 13")] #[should_panic(expected = "out of range for slice of length 13")]
fn test_buffer_out_of_bounds_write() { fn test_buffer_out_of_bounds_write() {
let lua = Lua::new(); let lua = Lua::new();
let buf = lua.create_buffer(b"hello, world!").unwrap(); let buf = lua.create_buffer(b"hello, world!").unwrap();
buf.write_bytes(14, b"!!"); buf.write_bytes(14, b"!!");
} }
#[test]
fn create_large_buffer() {
let lua = Lua::new();
let err = lua.create_buffer_with_capacity(1_073_741_824 + 1).unwrap_err(); // 1GB
assert!(err.to_string().contains("memory allocation error"));
// Normal buffer is okay
let buf = lua.create_buffer_with_capacity(1024 * 1024).unwrap();
assert_eq!(buf.len(), 1024 * 1024);
}
#[test]
fn test_buffer_cursor() -> Result<()> {
let lua = Lua::new();
let mut cursor = lua.create_buffer(b"hello, world")?.cursor();
let mut data = Vec::new();
cursor.read_to_end(&mut data)?;
assert_eq!(data, b"hello, world");
// No more data to read
let mut one = [0u8; 1];
assert_eq!(cursor.read(&mut one)?, 0);
// Seek to start
cursor.seek(SeekFrom::Start(0))?;
cursor.read_exact(&mut one)?;
assert_eq!(one, [b'h']);
// Seek to end -5
cursor.seek(SeekFrom::End(-5))?;
let mut five = [0u8; 5];
cursor.read_exact(&mut five)?;
assert_eq!(&five, b"world");
// Seek to current -1
cursor.seek(SeekFrom::Current(-1))?;
cursor.read_exact(&mut one)?;
assert_eq!(one, [b'd']);
// Invalid seek
assert!(cursor.seek(SeekFrom::Current(-100)).is_err());
assert!(cursor.seek(SeekFrom::End(1)).is_err());
// Write data
let buf = lua.create_buffer_with_capacity(100)?;
cursor = buf.clone().cursor();
cursor.write_all(b"hello, ...")?;
cursor.seek(SeekFrom::Current(-3))?;
cursor.write_all(b"Rust!")?;
assert_eq!(&buf.read_bytes::<12>(0), b"hello, Rust!");
// Writing beyond the end of the buffer does nothing
cursor.seek(SeekFrom::End(0))?;
assert_eq!(cursor.write(b".")?, 0);
// Flush is no-op
cursor.flush()?;
Ok(())
}
+10 -12
View File
@@ -21,6 +21,7 @@ fn test_chunk_methods() -> Result<()> {
} }
#[test] #[test]
#[cfg(not(target_os = "wasi"))]
fn test_chunk_path() -> Result<()> { fn test_chunk_path() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
@@ -119,12 +120,11 @@ fn test_compiler() -> Result<()> {
.set_debug_level(2) .set_debug_level(2)
.set_type_info_level(1) .set_type_info_level(1)
.set_coverage_level(2) .set_coverage_level(2)
.set_vector_lib("vector") .set_vector_ctor("vector.new")
.set_vector_ctor("new")
.set_vector_type("vector") .set_vector_type("vector")
.set_mutable_globals(vec!["mutable_global"]) .set_mutable_globals(["mutable_global"])
.set_userdata_types(vec!["MyUserdata"]) .set_userdata_types(["MyUserdata"])
.set_disabled_builtins(vec!["tostring"]); .set_disabled_builtins(["tostring"]);
assert!(compiler.compile("return tostring(vector.new(1, 2, 3))").is_ok()); assert!(compiler.compile("return tostring(vector.new(1, 2, 3))").is_ok());
@@ -142,16 +142,14 @@ fn test_compiler() -> Result<()> {
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
#[test] #[test]
fn test_compiler_library_constants() { fn test_compiler_library_constants() {
use mlua::{CompileConstant, Compiler, Vector}; use mlua::{Compiler, Vector};
let compiler = Compiler::new() let compiler = Compiler::new()
.set_optimization_level(2) .set_optimization_level(2)
.set_library_constants(vec![ .add_library_constant("mylib.const_bool", true)
("mylib", "const_bool", CompileConstant::Boolean(true)), .add_library_constant("mylib.const_num", 123.0)
("mylib", "const_num", CompileConstant::Number(123.0)), .add_library_constant("mylib.const_vec", Vector::zero())
("mylib", "const_vec", CompileConstant::Vector(Vector::zero())), .add_library_constant("mylib.const_str", "value1");
("mylib", "const_str", "value1".into()),
]);
let lua = Lua::new(); let lua = Lua::new();
lua.set_compiler(compiler); lua.set_compiler(compiler);
+22 -12
View File
@@ -1,15 +1,19 @@
error[E0596]: cannot borrow `s` as mutable, as it is a captured variable in a `Fn` closure error[E0596]: cannot borrow `s` as mutable, as it is a captured variable in a `Fn` closure
--> tests/compile/async_any_userdata_method.rs:9:49 --> tests/compile/async_any_userdata_method.rs:9:49
| |
9 | reg.add_async_method("t", |_, this, ()| async { 8 | let mut s = &s;
| ^^^^^ cannot borrow as mutable | ----- `s` declared here, outside the closure
9 | reg.add_async_method("t", |_, this, ()| async {
| ------------- ^^^^^ cannot borrow as mutable
| |
| in this closure
10 | s = &*this; 10 | s = &*this;
| - mutable borrow occurs due to use of `s` in closure | - mutable borrow occurs due to use of `s` in closure
error[E0373]: async block may outlive the current function, but it borrows `this`, which is owned by the current function error[E0373]: async block may outlive the current function, but it borrows `this`, which is owned by the current function
--> tests/compile/async_any_userdata_method.rs:9:49 --> tests/compile/async_any_userdata_method.rs:9:49
| |
9 | reg.add_async_method("t", |_, this, ()| async { 9 | reg.add_async_method("t", |_, this, ()| async {
| ^^^^^ may outlive borrowed value `this` | ^^^^^ may outlive borrowed value `this`
10 | s = &*this; 10 | s = &*this;
| ---- `this` is borrowed here | ---- `this` is borrowed here
@@ -17,7 +21,7 @@ error[E0373]: async block may outlive the current function, but it borrows `this
note: async block is returned here note: async block is returned here
--> tests/compile/async_any_userdata_method.rs:9:49 --> tests/compile/async_any_userdata_method.rs:9:49
| |
9 | reg.add_async_method("t", |_, this, ()| async { 9 | reg.add_async_method("t", |_, this, ()| async {
| _________________________________________________^ | _________________________________________________^
10 | | s = &*this; 10 | | s = &*this;
11 | | Ok(()) 11 | | Ok(())
@@ -25,13 +29,13 @@ note: async block is returned here
| |_________^ | |_________^
help: to force the async block to take ownership of `this` (and any other referenced variables), use the `move` keyword help: to force the async block to take ownership of `this` (and any other referenced variables), use the `move` keyword
| |
9 | reg.add_async_method("t", |_, this, ()| async move { 9 | reg.add_async_method("t", |_, this, ()| async move {
| ++++ | ++++
error: lifetime may not live long enough error: lifetime may not live long enough
--> tests/compile/async_any_userdata_method.rs:9:49 --> tests/compile/async_any_userdata_method.rs:9:49
| |
9 | reg.add_async_method("t", |_, this, ()| async { 9 | reg.add_async_method("t", |_, this, ()| async {
| ___________________________________-------------_^ | ___________________________________-------------_^
| | | | | | | |
| | | return type of closure `{async block@$DIR/tests/compile/async_any_userdata_method.rs:9:49: 9:54}` contains a lifetime `'2` | | | return type of closure `{async block@$DIR/tests/compile/async_any_userdata_method.rs:9:49: 9:54}` contains a lifetime `'2`
@@ -46,22 +50,28 @@ error: lifetime may not live long enough
error[E0597]: `s` does not live long enough error[E0597]: `s` does not live long enough
--> tests/compile/async_any_userdata_method.rs:8:21 --> tests/compile/async_any_userdata_method.rs:8:21
| |
7 | let s = String::new(); 7 | let s = String::new();
| - binding `s` declared here | - binding `s` declared here
8 | let mut s = &s; 8 | let mut s = &s;
| ^^ borrowed value does not live long enough | ^^ borrowed value does not live long enough
9 | / reg.add_async_method("t", |_, this, ()| async { 9 | / reg.add_async_method("t", |_, this, ()| async {
10 | | s = &*this; 10 | | s = &*this;
11 | | Ok(()) 11 | | Ok(())
12 | | }); 12 | | });
| |__________- argument requires that `s` is borrowed for `'static` | |__________- argument requires that `s` is borrowed for `'static`
13 | }) 13 | })
| - `s` dropped here while still borrowed | - `s` dropped here while still borrowed
|
note: requirement that the value outlives `'static` introduced here
--> src/userdata.rs
|
| M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
| ^^^^^^^
error[E0373]: closure may outlive the current function, but it borrows `s`, which is owned by the current function error[E0373]: closure may outlive the current function, but it borrows `s`, which is owned by the current function
--> tests/compile/async_any_userdata_method.rs:9:35 --> tests/compile/async_any_userdata_method.rs:9:35
| |
9 | reg.add_async_method("t", |_, this, ()| async { 9 | reg.add_async_method("t", |_, this, ()| async {
| ^^^^^^^^^^^^^ may outlive borrowed value `s` | ^^^^^^^^^^^^^ may outlive borrowed value `s`
10 | s = &*this; 10 | s = &*this;
| - `s` is borrowed here | - `s` is borrowed here
@@ -69,12 +79,12 @@ error[E0373]: closure may outlive the current function, but it borrows `s`, whic
note: function requires argument type to outlive `'static` note: function requires argument type to outlive `'static`
--> tests/compile/async_any_userdata_method.rs:9:9 --> tests/compile/async_any_userdata_method.rs:9:9
| |
9 | / reg.add_async_method("t", |_, this, ()| async { 9 | / reg.add_async_method("t", |_, this, ()| async {
10 | | s = &*this; 10 | | s = &*this;
11 | | Ok(()) 11 | | Ok(())
12 | | }); 12 | | });
| |__________^ | |__________^
help: to force the closure to take ownership of `s` (and any other referenced variables), use the `move` keyword help: to force the closure to take ownership of `s` (and any other referenced variables), use the `move` keyword
| |
9 | reg.add_async_method("t", move |_, this, ()| async { 9 | reg.add_async_method("t", move |_, this, ()| async {
| ++++ | ++++
@@ -1,10 +1,10 @@
error: lifetime may not live long enough error: lifetime may not live long enough
--> tests/compile/async_nonstatic_userdata.rs:9:13 --> tests/compile/async_nonstatic_userdata.rs:9:13
| |
7 | impl UserData for MyUserData<'_> { 7 | impl UserData for MyUserData<'_> {
| -- lifetime `'1` appears in the `impl`'s self type | -- lifetime `'1` appears in the `impl`'s self type
8 | fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) { 8 | fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
9 | / methods.add_async_method("print", |_, data, ()| async move { 9 | / methods.add_async_method("print", |_, data, ()| async move {
10 | | println!("{}", data.0); 10 | | println!("{}", data.0);
11 | | Ok(()) 11 | | Ok(())
12 | | }); 12 | | });
+11 -11
View File
@@ -1,14 +1,14 @@
error[E0277]: the type `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary error[E0277]: the type `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
--> tests/compile/lua_norefunwindsafe.rs:7:18 --> tests/compile/lua_norefunwindsafe.rs:7:18
| |
7 | catch_unwind(|| lua.create_table().unwrap()); 7 | catch_unwind(|| lua.create_table().unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
| | | |
| required by a bound introduced by this call | required by a bound introduced by this call
| |
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::raw::RawLua>`, which is required by `{closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:20}: UnwindSafe` = help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::raw::RawLua>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>` note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs --> $CARGO/lock_api-$VERSION/src/remutex.rs
| |
| pub struct ReentrantMutex<R, G, T: ?Sized> { | pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^
@@ -20,7 +20,7 @@ note: required because it appears within the type `alloc::sync::ArcInner<lock_ap
note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>` note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>`
--> $RUST/core/src/marker.rs --> $RUST/core/src/marker.rs
| |
| pub struct PhantomData<T: ?Sized>; | pub struct PhantomData<T: PointeeSized>;
| ^^^^^^^^^^^ | ^^^^^^^^^^^
note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>` note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs --> $RUST/alloc/src/sync.rs
@@ -44,27 +44,27 @@ note: required by a bound in `std::panic::catch_unwind`
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> { | pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^^^^^^^^^^ required by this bound in `catch_unwind` | ^^^^^^^^^^ required by this bound in `catch_unwind`
error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
--> tests/compile/lua_norefunwindsafe.rs:7:18 --> tests/compile/lua_norefunwindsafe.rs:7:18
| |
7 | catch_unwind(|| lua.create_table().unwrap()); 7 | catch_unwind(|| lua.create_table().unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
| | | |
| required by a bound introduced by this call | required by a bound introduced by this call
| |
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`, which is required by `{closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:20}: UnwindSafe` = help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`
note: required because it appears within the type `Cell<usize>` note: required because it appears within the type `Cell<usize>`
--> $RUST/core/src/cell.rs --> $RUST/core/src/cell.rs
| |
| pub struct Cell<T: ?Sized> { | pub struct Cell<T: ?Sized> {
| ^^^^ | ^^^^
note: required because it appears within the type `lock_api::remutex::RawReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId>` note: required because it appears within the type `lock_api::remutex::RawReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs --> $CARGO/lock_api-$VERSION/src/remutex.rs
| |
| pub struct RawReentrantMutex<R, G> { | pub struct RawReentrantMutex<R, G> {
| ^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>` note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs --> $CARGO/lock_api-$VERSION/src/remutex.rs
| |
| pub struct ReentrantMutex<R, G, T: ?Sized> { | pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^
@@ -76,7 +76,7 @@ note: required because it appears within the type `alloc::sync::ArcInner<lock_ap
note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>` note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>`
--> $RUST/core/src/marker.rs --> $RUST/core/src/marker.rs
| |
| pub struct PhantomData<T: ?Sized>; | pub struct PhantomData<T: PointeeSized>;
| ^^^^^^^^^^^ | ^^^^^^^^^^^
note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>` note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
--> $RUST/alloc/src/sync.rs --> $RUST/alloc/src/sync.rs
+1 -1
View File
@@ -8,7 +8,7 @@ error[E0277]: `Rc<Cell<i32>>` cannot be sent between threads safely
| | within this `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}` | | within this `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`
| required by a bound introduced by this call | required by a bound introduced by this call
| |
= help: within `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`, the trait `Send` is not implemented for `Rc<Cell<i32>>`, which is required by `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}: MaybeSend` = help: within `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`, the trait `Send` is not implemented for `Rc<Cell<i32>>`
note: required because it's used within this closure note: required because it's used within this closure
--> tests/compile/non_send.rs:11:25 --> tests/compile/non_send.rs:11:25
| |
+23 -23
View File
@@ -1,14 +1,14 @@
error[E0277]: the type `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary error[E0277]: the type `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
--> tests/compile/ref_nounwindsafe.rs:8:18 --> tests/compile/ref_nounwindsafe.rs:8:18
| |
8 | catch_unwind(move || table.set("a", "b").unwrap()); 8 | catch_unwind(move || table.set("a", "b").unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
| | | |
| required by a bound introduced by this call | required by a bound introduced by this call
| |
= help: within `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::raw::RawLua>`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe` = help: within `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::raw::RawLua>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>` note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs --> $CARGO/lock_api-$VERSION/src/remutex.rs
| |
| pub struct ReentrantMutex<R, G, T: ?Sized> { | pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^
@@ -23,16 +23,16 @@ note: required because it appears within the type `std::sync::Weak<lock_api::rem
| |
| pub struct Weak< | pub struct Weak<
| ^^^^ | ^^^^
note: required because it appears within the type `mlua::state::WeakLua` note: required because it appears within the type `WeakLua`
--> src/state.rs --> src/state.rs
| |
| pub(crate) struct WeakLua(XWeak<ReentrantMutex<RawLua>>); | pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^ | ^^^^^^^
note: required because it appears within the type `mlua::types::ValueRef` note: required because it appears within the type `mlua::types::value_ref::ValueRef`
--> src/types.rs --> src/types/value_ref.rs
| |
| pub(crate) struct ValueRef { | pub struct ValueRef {
| ^^^^^^^^ | ^^^^^^^^
note: required because it appears within the type `LuaTable` note: required because it appears within the type `LuaTable`
--> src/table.rs --> src/table.rs
| |
@@ -49,27 +49,27 @@ note: required by a bound in `std::panic::catch_unwind`
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> { | pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^^^^^^^^^^ required by this bound in `catch_unwind` | ^^^^^^^^^^ required by this bound in `catch_unwind`
error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
--> tests/compile/ref_nounwindsafe.rs:8:18 --> tests/compile/ref_nounwindsafe.rs:8:18
| |
8 | catch_unwind(move || table.set("a", "b").unwrap()); 8 | catch_unwind(move || table.set("a", "b").unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
| | | |
| required by a bound introduced by this call | required by a bound introduced by this call
| |
= help: within `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`, which is required by `{closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:25}: UnwindSafe` = help: within `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`
note: required because it appears within the type `Cell<usize>` note: required because it appears within the type `Cell<usize>`
--> $RUST/core/src/cell.rs --> $RUST/core/src/cell.rs
| |
| pub struct Cell<T: ?Sized> { | pub struct Cell<T: ?Sized> {
| ^^^^ | ^^^^
note: required because it appears within the type `lock_api::remutex::RawReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId>` note: required because it appears within the type `lock_api::remutex::RawReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs --> $CARGO/lock_api-$VERSION/src/remutex.rs
| |
| pub struct RawReentrantMutex<R, G> { | pub struct RawReentrantMutex<R, G> {
| ^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>` note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
--> $CARGO/lock_api-0.4.12/src/remutex.rs --> $CARGO/lock_api-$VERSION/src/remutex.rs
| |
| pub struct ReentrantMutex<R, G, T: ?Sized> { | pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^
@@ -84,16 +84,16 @@ note: required because it appears within the type `std::sync::Weak<lock_api::rem
| |
| pub struct Weak< | pub struct Weak<
| ^^^^ | ^^^^
note: required because it appears within the type `mlua::state::WeakLua` note: required because it appears within the type `WeakLua`
--> src/state.rs --> src/state.rs
| |
| pub(crate) struct WeakLua(XWeak<ReentrantMutex<RawLua>>); | pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
| ^^^^^^^ | ^^^^^^^
note: required because it appears within the type `mlua::types::ValueRef` note: required because it appears within the type `mlua::types::value_ref::ValueRef`
--> src/types.rs --> src/types/value_ref.rs
| |
| pub(crate) struct ValueRef { | pub struct ValueRef {
| ^^^^^^^^ | ^^^^^^^^
note: required because it appears within the type `LuaTable` note: required because it appears within the type `LuaTable`
--> src/table.rs --> src/table.rs
| |
+9 -9
View File
@@ -1,24 +1,24 @@
error[E0373]: closure may outlive the current function, but it borrows `inner`, which is owned by the current function error[E0373]: closure may outlive the current function, but it borrows `inner`, which is owned by the current function
--> tests/compile/scope_callback_capture.rs:7:43 --> tests/compile/scope_callback_capture.rs:7:43
| |
5 | lua.scope(|scope| { 5 | lua.scope(|scope| {
| ----- has type `&'1 mut mlua::scope::Scope<'1, '_>` | ----- has type `&'1 mlua::Scope<'1, '_>`
6 | let mut inner: Option<Table> = None; 6 | let mut inner: Option<Table> = None;
7 | let f = scope.create_function_mut(|_, t: Table| { 7 | let f = scope.create_function_mut(|_, t: Table| {
| ^^^^^^^^^^^^^ may outlive borrowed value `inner` | ^^^^^^^^^^^^^ may outlive borrowed value `inner`
8 | inner = Some(t); 8 | inner = Some(t);
| ----- `inner` is borrowed here | ----- `inner` is borrowed here
| |
note: function requires argument type to outlive `'1` note: function requires argument type to outlive `'1`
--> tests/compile/scope_callback_capture.rs:7:17 --> tests/compile/scope_callback_capture.rs:7:17
| |
7 | let f = scope.create_function_mut(|_, t: Table| { 7 | let f = scope.create_function_mut(|_, t: Table| {
| _________________^ | _________________^
8 | | inner = Some(t); 8 | | inner = Some(t);
9 | | Ok(()) 9 | | Ok(())
10 | | })?; 10 | | })?;
| |__________^ | |__________^
help: to force the closure to take ownership of `inner` (and any other referenced variables), use the `move` keyword help: to force the closure to take ownership of `inner` (and any other referenced variables), use the `move` keyword
| |
7 | let f = scope.create_function_mut(move |_, t: Table| { 7 | let f = scope.create_function_mut(move |_, t: Table| {
| ++++ | ++++
+2 -2
View File
@@ -1,8 +1,8 @@
error[E0373]: closure may outlive the current function, but it borrows `test.field`, which is owned by the current function error[E0373]: closure may outlive the current function, but it borrows `test.field`, which is owned by the current function
--> tests/compile/scope_invariance.rs:13:39 --> tests/compile/scope_invariance.rs:13:39
| |
9 | lua.scope(|scope| { 9 | lua.scope(|scope| {
| ----- has type `&'1 mut mlua::scope::Scope<'1, '_>` | ----- has type `&'1 mlua::Scope<'1, '_>`
... ...
13 | scope.create_function_mut(|_, ()| { 13 | scope.create_function_mut(|_, ()| {
| ^^^^^^^ may outlive borrowed value `test.field` | ^^^^^^^ may outlive borrowed value `test.field`
+7 -1
View File
@@ -2,7 +2,7 @@ error[E0499]: cannot borrow `i` as mutable more than once at a time
--> tests/compile/scope_mutable_aliasing.rs:12:51 --> tests/compile/scope_mutable_aliasing.rs:12:51
| |
10 | lua.scope(|scope| { 10 | lua.scope(|scope| {
| ----- has type `&mut mlua::scope::Scope<'_, '1>` | ----- has type `&mlua::Scope<'_, '1>`
11 | let _a = scope.create_userdata(MyUserData(&mut i)).unwrap(); 11 | let _a = scope.create_userdata(MyUserData(&mut i)).unwrap();
| ----------------------------------------- | -----------------------------------------
| | | | | |
@@ -10,3 +10,9 @@ error[E0499]: cannot borrow `i` as mutable more than once at a time
| argument requires that `i` is borrowed for `'1` | argument requires that `i` is borrowed for `'1`
12 | let _b = scope.create_userdata(MyUserData(&mut i)).unwrap(); 12 | let _b = scope.create_userdata(MyUserData(&mut i)).unwrap();
| ^^^^^^ second mutable borrow occurs here | ^^^^^^ second mutable borrow occurs here
|
note: requirement that the value outlives `'1` introduced here
--> src/scope.rs
|
| T: UserData + 'env,
| ^^^^
+7 -1
View File
@@ -2,7 +2,7 @@ error[E0597]: `ibad` does not live long enough
--> tests/compile/scope_userdata_borrow.rs:15:46 --> tests/compile/scope_userdata_borrow.rs:15:46
| |
11 | lua.scope(|scope| { 11 | lua.scope(|scope| {
| ----- has type `&mut mlua::scope::Scope<'_, '1>` | ----- has type `&mlua::Scope<'_, '1>`
... ...
14 | let ibad = 42; 14 | let ibad = 42;
| ---- binding `ibad` declared here | ---- binding `ibad` declared here
@@ -13,3 +13,9 @@ error[E0597]: `ibad` does not live long enough
| argument requires that `ibad` is borrowed for `'1` | argument requires that `ibad` is borrowed for `'1`
16 | }; 16 | };
| - `ibad` dropped here while still borrowed | - `ibad` dropped here while still borrowed
|
note: requirement that the value outlives `'1` introduced here
--> src/scope.rs
|
| T: UserData + 'env,
| ^^^^

Some files were not shown because too many files have changed in this diff Show More