LuaCATS definitions
xLua can write LuaCATS definition
files for the C# API your Lua code calls. Editors that use
lua-language-server (LuaLS),
such as VS Code with the Lua extension, read them to complete CS.* names,
show signatures and report wrong argument types or misspelled members.
Generate the files
Section titled “Generate the files”In the Unity editor, run XLua > Generate LuaCATS Definitions. The files go
to <project>/LuaTypes/, next to Assets/. That folder sits outside Assets,
so Unity never imports the files and no xLua loader picks them up as scripts.
Generate again after you change the LuaCallCSharp or ReflectionUse
configuration and Unity has compiled it. The generator deletes files it wrote
earlier for types that are gone and leaves other files in the folder alone. XLua > Clear and Generate
LuaCATS Definitions first deletes every file the generator wrote in the
folder and its subfolders, then generates. It recognizes them by their first
two lines, ---@meta and the -- Generated by xLua LuaCATS generator marker;
other files stay.
The generator loads the native xLua plugin and asks it which Lua it runs. With
Lua 5.3 or later, C# integral types become integer; with LuaJIT they become
number, except long and ulong (see below).
Outside Unity, XLuaGenerate.exe (built by Tools/build_tools.py) takes
--luacats and the options below before its usual arguments:
mono Tools/XLuaGenerate.exe --luacats [--strict] [--suffix .d.lua] [--luarc path | --no-luarc] [--clear] \ Game.dll Game.Editor.dll LuaTypes/ [search paths...]The output does not depend on the machine: files are UTF-8 without BOM with
\n line ends, members are sorted, and the current culture is not used.
With the source generator
Section titled “With the source generator”The definitions come from the xLua source generator (Configuration).
It writes a binding manifest into every assembly it compiles
(XLua.Manifest.g.cs, compiled only in the Editor and with XLUA_GENERAL;
see Custom code generation),
and the definitions for that assembly come from it:
- the types it wraps, with exactly the members their wrappers bind, so the definitions and the bindings cannot disagree;
- its
[ReflectionUse]types and itsBlackListentries by type, which also hide members of the base classes declared in full; - the extension methods of its extension method map, for types Lua reaches through reflection;
DoNotGenmembers, and the members the generator leaves to reflection (infoXLSG004, such asinitsetters), which xLua still reaches.
An assembly without a manifest declares nothing: configuration lists are not read, and an assembly that the generator did not compile has no bindings to describe. The console says which assemblies came from a manifest, and a run that finds none says that only xLua’s own API is declared. Generate the definitions again after the assemblies recompiled.
Options
Section titled “Options”Each option is a field of CSObjectWrapEditor.GeneratorConfig. In Unity, set
it by marking a static field or property of a static editor class with the
attribute, as for GenPath (Configuration):
| Attribute (type) | Default | XLuaGenerate |
Effect |
|---|---|---|---|
LuaCATSPath (string) |
<project>/LuaTypes/ |
output path argument | Output folder. |
LuaCATSFileSuffix (string) |
.lua |
--suffix |
Ending of the file names, such as .d.lua. It must end in .lua, the only files LuaLS reads. xlua/util.lua keeps its name so that require 'xlua.util' resolves. |
LuaCATSStrict (bool) |
false |
--strict |
Strict CS names, see below. |
LuaCATSLuarcPath (string) |
.luarc.json in the folder that holds the output folder |
--luarc, --no-luarc |
The .luarc.json to write; an empty string turns it off. |
public static class LuaCATSConfig{ [CSObjectWrapEditor.LuaCATSPath] public static string Path = System.IO.Path.GetFullPath(UnityEngine.Application.dataPath + "/../Tools/LuaTypes"); [CSObjectWrapEditor.LuaCATSFileSuffix] public static string Suffix = ".d.lua"; [CSObjectWrapEditor.LuaCATSStrict] public static bool Strict = true; [CSObjectWrapEditor.LuaCATSLuarcPath] public static string Luarc = ""; // keep my own .luarc.json}Point the editor at them
Section titled “Point the editor at them”The generator writes a .luarc.json next to the output folder, by default in
the Unity project folder, with the two settings LuaLS needs:
{ "runtime.version": "Lua 5.5", "workspace.library": [ "LuaTypes" ]}runtime.version is Lua 5.5, Lua 5.4, Lua 5.3 or LuaJIT, after the
plugin; Lua 5.5 needs LuaLS 3.16 or later. The library path is relative to
the folder of the .luarc.json, which LuaLS resolves against the workspace
root, so open that folder in the editor.
The generator never overwrites a .luarc.json you wrote. JSON has no comments,
so it recognizes its own file by its exact content: those two settings and
nothing else. Once you add a setting, the file is yours, and the generator only
prints a hint when runtime.version or the library entry is missing. It also
writes nothing when a .luarc.jsonc is in the same folder, because LuaLS then
reads only one of the two files.
LuaLS treats library files as read-only definitions; it does not report
problems in them. To check scripts from the command line, run
lua-language-server --check=<folder> --checklevel=Warning.
Strict CS names
Section titled “Strict CS names”By default, CS and every namespace table carry ---@field [string] any: xLua
reaches any C# type through reflection, so CS.System.Console is valid at run
time even when it is not configured, and LuaLS types it any.
With LuaCATSStrict, those fallbacks are left out. LuaLS then reports every
CS name that is not a configured type, one of its base classes or a namespace
of them, as undefined-field, which catches misspelled type names. Names that
work through reflection are reported too, as are generic type definitions such
as CS.System.Collections.Generic.List(CS.System.Int32). Configure the types
you use, or silence a line with ---@diagnostic disable-next-line: undefined-field.
Validate Setup
Section titled “Validate Setup”XLua > Validate Setup reports configuration problems as warnings in the
console and changes nothing. Scripts and tests call
CSObjectWrapEditor.SetupValidator.Validate(), which returns the messages, and
XLuaGenerate.exe --validate prints them and exits with 1. It checks:
- the native plugin loads and its
xlua_get_lib_versionmatches the version the C# code expects; - every copy of
XLua.SourceGenerator.dllhas theRoslynAnalyzerlabel (Unity only); - every assembly with xLua configuration (
[LuaCallCSharp],[CSharpCallLua],[GCOptimize]or[ReflectionUse]on a type or at assembly level) has generated bindings. The message gives the likely cause: the source generator is not installed (with the command that installs it), or it did not run for that assembly (warning CS9057, a lostRoslynAnalyzerlabel, an assembly that does not referenceXLua); - the
GenPathfolder holds no output of the removed Generate Code (XLuaGenAutoRegister.cs,DelegatesGensBridge.csorWrapPusher.cs), which no longer compiles; - asmdefs under
Assetswhose scripts use xLua referenceXLua, andXLua.Coretoo when the source generator is installed and they hold configuration (Unity only); - the LuaCATS output folder can be created and written (it creates and deletes
a temporary file), the file suffix ends in
.lua, and an existing.luarc.jsonyou wrote has the settings for the plugin; - with
HOTFIX_ENABLE,Tools/XLuaHotfixInject.exeandMono.Cecil.dllexist; - no
Resourcesfolder underAssetsorPackagesholds a Lua module that earlier xLua versions shipped inAssets/XLua/Resourcesand an in-place upgrade left behind:xlua/util.lua.*andperf/profiler.lua.*, which would replace the modules now embedded in xLua because the Resources searcher runs first, andperf/memory.lua.*andtdr/tdr.lua.*, whose modules were removed (see MIGRATING.md). The warning says whether the file is unchanged from a version xLua shipped (delete it and its.meta) or differs (keep it only if you edited it on purpose). Folders Unity does not import (names starting with.or ending in~) are skipped. WithXLUA_NO_UTILaResourcescopy ofxlua.utilis how the module is provided, so it is not reported.XLuaGenerate.exe --validatechecks this only for the folders given with--resources-root(repeatable); - every configuration list (a static field or property with a configuration attribute) is reported once: the source generator does not read it, so convert it with XLua > Migrate Configuration to Attributes and delete it (Converting configuration lists);
- the entries of
BlackListlists, which the Editor’s reflection wrappers still read, name a type that exists and a member, or an overload, that the type declares. These entries are strings, so after a rename they silently stop working. For the attribute forms the compiler reports such entries as warningXLSG003, with file and line.
The configuration checks run only when the plugin passed, since reading the configuration starts a Lua state.
What gets declared
Section titled “What gets declared”The definitions declare the types the manifests list: every type the source
generator wraps (LuaCallCSharp), with the members its manifest lists, and
every ReflectionUse type, with the BlackList and [Obsolete] filtering.
They add the base classes of those types in full, since Lua reaches their
members through every derived object. Members listed in DoNotGen stay,
because xLua still reaches them through reflection.
| File | Contents |
|---|---|
<Namespace>.<Type>.lua |
One file per C# type. |
CS.lua |
The CS root and namespace tables, delegate aliases, and empty classes for types that only appear in signatures. |
xlua.lua |
xlua.* helpers, typeof, cast, base, uint64, the array type and, on LuaJIT, xlua.Int64 (and xlua.UInt64). |
xlua/util.lua |
The module returned by require 'xlua.util'. |
One file per type keeps each file well under the 500 KB LuaLS
workspace.preloadFileSize limit, even for large namespaces such as
UnityEngine.
Type mapping
Section titled “Type mapping”| C# | LuaCATS |
|---|---|
bool |
boolean |
sbyte, byte, short, ushort, int, uint, char |
integer (Lua 5.3+) or number (LuaJIT) |
long, ulong |
integer (Lua 5.3+) or xlua.Int64 (LuaJIT; parameters take number|xlua.Int64); ulong is xlua.UInt64 on LuaJIT built with UINT_ESPECIALLY |
float, double |
number |
string, byte[] |
string |
object |
any |
void return |
no @return |
T? (Nullable<T>) |
T? |
LuaTable, LuaFunction |
table, function |
IntPtr |
lightuserdata |
| class, struct, interface | CS.Namespace.Type; parents are the base class and the implemented interfaces that appear in the files |
nested type Outer.Inner |
CS.Namespace.Outer.Inner |
constructed generic, such as List<int> |
CS.System.Collections.Generic.List_System_Int32 |
T[] |
xlua.Array<T>: zero-based [integer] access and Length |
| delegate | ---@alias CS.Namespace.Delegate fun(...) |
| enum | a class whose values are fields of the enum type, plus __CastFrom |
A parameter that xLua converts from a Lua table also accepts table: structs,
classes with a public parameterless constructor, interfaces and arrays (T[]).
LuaJIT has no 64-bit integers, so xLua passes long and ulong to Lua as a
userdata, declared as the class xlua.Int64, and takes either that userdata or
a number back. Writable long fields and properties are typed
number|xlua.Int64, since LuaCATS has one type for reads and writes. +, -,
*, /, %, ^, unary -, tostring and .. work with a number on either
side (.. also with a string). ==, < and <= only compare two
xlua.Int64 values: x == 0 is always false and x < 1 raises an error, and
LuaLS reports neither (see the 64-bit integer section of the
tutorial for the alternatives). The LuaJIT output also
declares the uint64 helpers that only LuaJIT builds have: band, bor,
bxor, bnot, lshift, rshift, arshift and idiv. Values that Lua
supplies take a number too: indexer keys, the operand of a C# operator, and
what a Lua function returns for a delegate. A delegate alias describes the Lua
function, so its parameters are xlua.Int64; when Lua calls a C# delegate, a
long result is typed number|xlua.Int64.
A LuaJIT plugin built with -DUINT_ESPECIALLY=ON (default off) pushes ulong
as a separate unsigned kind of the userdata. The generator detects this
through the plugin (tostring(uint64.parse('1')) is 1U) and declares
ulong as xlua.UInt64, with parameters taking number|xlua.UInt64;
uint64.parse, uint64.divide and uint64.remainder return it, and the
LuaJIT-only helpers have overloads that return it for an xlua.UInt64
operand. It has the
same operators, unsigned, but unary - returns an xlua.Int64, tostring
appends U, and a number of 2^63 or more saturates to 2^63 - 1 before use.
Any operator with one xlua.Int64 and one xlua.UInt64 raises “type not
match”, which LuaLS does not report; it does report passing one where the
other is declared. xLua’s argument checks reject the other kind, but fields,
single-overload methods and array tables convert it without a check and
reinterpret the bits.
Members follow the Lua call syntax:
- Fields and properties are
---@fieldentries, markedstaticorread-onlyin their description. A name that is not a Lua identifier, such as a keyword or a name with non-ASCII letters, is quoted:---@field ['Şehir'] integer, which matches theobj['Şehir']that Lua code has to use. - Instance methods use
:and static methods use.. Overloads become---@overloadentries in xLua’s resolution order. Optional parameters end in?, andparams T[]becomes...: T. outparameters leave the argument list. The return value comes first, followed by everyoutandrefparameter in order, as xLua returns them.- Constructors are
---@overload fun(...): CS.Typeon the class, soCS.UnityEngine.GameObject('name')type-checks. Structs also getfun(). - An event is a method that takes
"+"or"-"and a handler:obj:Clicked('+', handler). - Indexers with a non-string key become
---@field [integer] T, and+,-,*,/,%and the bitwise operators become---@operatorentries. - Extension methods from configured static classes appear as instance methods of the type they extend.
- Generic methods appear only when xLua can call them: every type parameter
is constrained to a class and appears as a parameter type. xLua calls them
with each type argument set to its first constraint, not inferred from the
arguments, so
T Clone<T>(T source) where T : Shape, new()runs asClone<Shape>and may return aShapefor aCircle. The signature therefore uses the constraint type (CS.Shape), not---@generic T, which would claim the result has the argument’s type.
Limitations
Section titled “Limitations”- LuaCATS has one class per name, so instances also show static members, and the class table also shows instance members and the call signature.
- By default the namespace tables accept any key (
[string] any). xLua reaches every C# type through reflection, soCS.System.Consoleis valid even when it is not configured. A misspelled type name is therefore not reported, and LuaLS also skips undefined-member checks on a chain read straight through a namespace, such asCS.Game.Color.Purple. Keep the class in a local (local Color = CS.Game.Color) andColor.Purpleis reported, or turn on strictCSnames. - LuaLS picks an overload by argument count before it checks argument types. When two C# overloads take the same number of arguments, or none takes the number you pass, it reports nothing.
- Helper parameters that take a C# class table, such as the one for
xlua.private_accessible, have typeany, so LuaLS does not report a missing argument there. - Generic type definitions are not declared.
CS.System.Collections.Generic.List(CS.System.Int32)has typeany; annotate the result with---@typeto get the generated class. - Operators whose left operand is not the declaring type, such as
2 * vector, and comparison operators have no LuaCATS form. - Types that appear only in signatures are empty classes, so their members are
reported as undefined. Add those types to
LuaCallCSharporReflectionUseto declare them. - LuaCATS has no read-only fields; assigning a read-only property is not reported.
xlua.cast(obj, typeof(T))changes the object in place and returns nothing. Use---@cast obj CS.Tto tell the editor about the new type.
