Compatibility
A Luau library written for Luau 0.730 or earlier runs in Luwu without changes. The reverse doesn't hold: once a file uses classes or table comprehensions it's Luwu code that upstream Luau can't run. Luwu tells the two apart by file extension and refuses Luwu syntax in a .luau file so that file stays valid Luau.
Luau code in Luwu
Luwu accepts valid Luau code up to and including Luau 0.730 along with every RFC upstream accepted by 2026-07-17. RFCs accepted upstream after 0.730 aren't covered by that guarantee. UPSTREAM.md in the Luwu repository lists which upstream RFCs Luwu implements, which it replaced and which it skipped.
Where Luau and Luwu diverge
Since the fork, Luau and Luwu have diverged. The main differences today are in classes and inheritance, integers, and destructuring:
- Classes and inheritance. Upstream's classes RFC adds implementation inheritance through
open classandextends. Its private members have to start with_and are read through new._and:_operators that only exist inside methods. Luwu's classes have no inheritance and share code through traits instead. Private members keep ordinary names, and every method call checks thatselfreally is an object of its class. - Integers. Upstream's 64-bit integer type is a single signed
integerwith no arithmetic operators: adding two of them takesinteger.add(a, b). It was never enabled on Roblox and isn't covered by Luau's compatibility promise. Luwu is building a replacement with separate signed and unsigned types, arithmetic operators and bigints. - Destructuring. Upstream accepted key destructuring in 2024 and withdrew it a month later. Its current proposal mirrors table constructors and writes every name twice:
local {name = name, kind = kind} = entry. In Luwu,const .{ name, kind } = entrywrites each name once and works on tables, objects and userdata alike.
File extensions
Luwu takes every module's language from its file extension. The command-line tools and the language server agree on it:
| Extension | Language |
|---|---|
.luwu | Luwu |
.luau | Luau |
.lua | Lua |
A project can mix them freely: a .luwu module can require a .luau one and the other way around.
Luwu syntax in Luau files
Luwu could accept Luwu syntax everywhere, but then a .luau file could quietly stop being Luau. It would still run in Luwu and break the day someone opens it in upstream tooling or in Roblox. So Luwu refuses: in a .luau module, Luwu-only syntax is a parse error that says what to do instead.
local fruit = { "apple", "kiwi", "pear" }
local long_names = { for _, name in fruit when #name > 4 give name }
table.drop(fruit, "kiwi")
print(#long_names, #fruit)Both messages come straight from luwu-analyze with the file saved as inventory.luau. Rename it to inventory.luwu and the same code checks cleanly.
Library functions aren't syntax for the parser to reject. Instead the LuwuOnlyApi lint warns about Luwu-only functions like table.drop and class.of in Luau and Lua files. The language server does the same for Luwu-only directives such as --!trust.
luwuOnly
Behind those errors, the parser marks every syntax node that only Luwu accepts with a luwuOnly flag. It goes on the smallest node whose own syntax is Luwu's: a default argument marks its function but leaves the function's body alone. Children of a marked node aren't marked unless their own syntax is Luwu-only as well.
Tools can read the flag through Luwu's AST reflection (node:luwuOnly()). That's how a transpiler from Luwu to Luau finds exactly the nodes it has to rewrite and leaves everything else as it was.