Skip to content

About

Extract Lua type stubs from mlua UserData impls using rustc's type inference

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

mlua-typegen

Automatically generate Lua type stubs from your Rust mlua UserData implementations. Get full autocomplete and type checking in LuaLS or EmmyLua with zero manual annotation.

How it works

mlua-typegen is a rustc driver (like clippy or miri) that hooks into compilation after type checking, when all types are fully resolved. It walks the HIR to find impl UserData blocks, extracts method signatures and field types using rustc's type inference, and generates LuaCATS stub files.

No attributes, no macros, no code changes required.

Example

Given this Rust code:

impl UserData for Vec2 {
    fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
        fields.add_field_method_get("x", |_, this| Ok(this.x));
        fields.add_field_method_get("y", |_, this| Ok(this.y));
    }

    fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
        methods.add_method("dot", |_, this, (other_x, other_y): (f64, f64)| {
            Ok(this.x * other_x + this.y * other_y)
        });
        methods.add_method("components", |_, this, ()| {
            Ok((this.x, this.y))
        });
    }
}

It generates:

---@meta

---@class (exact) Vec2
---@field x number (readonly)
---@field y number (readonly)
local Vec2 = {}

---@param other_x number
---@param other_y number
---@return number
Vec2["dot"] = function(self, other_x, other_y) end

---@return number x
---@return number y
Vec2["components"] = function(self) end

Install

If Rust came from rustup, the installer handles everything else. It installs the pinned private nightly and compiler components without changing your default toolchain.

Linux and macOS:

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/raw/themixednuts/mlua-typegen/main/scripts/install.sh | sh

Windows PowerShell:

irm https://raw.githubusercontent.com/themixednuts/mlua-typegen/main/scripts/install.ps1 | iex

The installer builds cargo-mlua-typegen and its rustc driver once. Later runs check the private toolchain and restore a missing component automatically. Your project can keep using stable Rust.

Usage

# Generate ./lua-types/mlua-typegen.lua
cargo mlua-typegen

# Specify output directory
cargo mlua-typegen --output types/

# Target a specific crate in a workspace
cargo mlua-typegen -p my-plugin-crate

# Generate EmmyLua-flavored annotations
cargo mlua-typegen --emmylua

# Use a non-default workspace mapping file
cargo mlua-typegen --config config/mlua-typegen.toml

Run cargo mlua-typegen --help for generator options. Cargo package and workspace flags pass through to the internal cargo check.

Each successful run publishes one complete mlua-typegen.lua snapshot. Compiler processes write only to private per-run report directories; a failed build leaves the previous public snapshot unchanged. If upgrading from the former multi-file layout, remove the old generated output directory once before regenerating.

Then point your language server at the output:

// .luarc.json (LuaLS)
{
    "workspace.library": ["lua-types"]
}
// .emmyrc.json (EmmyLua)
{
    "workspace": {
        "library": ["lua-types"]
    }
}

What it extracts

  • Classes from impl UserData blocks
  • Fields from add_field_method_get / add_field_method_set (tracks readonly vs writable)
  • Methods from add_method / add_function (instance vs static)
  • Async methods from add_async_method / add_async_function
  • Metamethods from add_meta_method (__tostring, __add, __len, etc.)
  • Enums from Rust enums with string-based IntoLua / FromLua impls
  • Modules from create_table / scope-based function registration
  • Doc comments carried through to the generated stubs
  • Overloaded methods emitted with @overload annotations
  • Named return values extracted from tuple multi-returns (e.g. ---@return number x)
  • Union types from match/if branches returning different types (e.g. string | integer)
  • AnyUserData resolution — traces through create_any_userdata(expr) to resolve the concrete class
  • into_lua_multi decomposition — expands tuple returns into multiple @return annotations

Explicit type mappings

Place mlua-typegen.toml at the Cargo workspace root, or select exactly one file with --config PATH. Start the file with Tombi's schema directive so the editor validates the generated contract directly:

#:schema https://github.com/raw/themixednuts/mlua-typegen/main/schemas/mlua-typegen.schema.json

[[mappings]]
id = "uuid"
rust = { package = "uuid", version = "1", path = "Uuid" }
lua = "string"

[[mappings]]
id = "wire-id"
rust = { package = "domain", path = "WireId" }
from_lua = "string"
into_lua = "integer"

[[external_types]]
name = "Promise"
params = ["T"]

lua asserts the same FromLua and IntoLua type. Directional fields may be declared separately. Expressions support unions, optionals, arrays, maps, functions, named types, and declared Rust parameters such as $T. A genuinely runtime-erased value must use dynamic("reason"); bare any is invalid.

The schema is generated from the Rust configuration types with cargo run --example generate-config-schema. Its URL remains stable; the project does not use or pre-bump numeric schema versions. See the explicit type-mapping decision.

Native type mapping

Rust Lua
String, &str, Cow<str> string
i8..i128, u8..u128, usize, isize integer
f32, f64 number
bool boolean
Vec<T>, VecDeque<T> T[]
Option<T> T?
HashMap<K, V>, BTreeMap<K, V> table<K, V>
Result<T, _> T (unwrapped)
Box<T>, Arc<T>, Rc<T>, Mutex<T>, RwLock<T> T (unwrapped)
mlua::Table table
mlua::Function function
mlua::Thread thread
mlua::Value any with native dynamic evidence
mlua::Error string
mlua::AnyUserData resolved registered userdata identity

External crate semantics such as uuid::Uuid are not guessed from names or suffixes; declare them explicitly when native or extracted evidence cannot prove the Lua type.

Real-world examples

The examples/ directory contains generated stubs for:

  • Lune — Roblox datatypes (CFrame, Vector3, Color3, etc.) and standard library modules
  • Yazi — file manager plugin API with 200+ class methods

License

MIT

About

Extract Lua type stubs from mlua UserData impls using rustc's type inference

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages