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:

File extensions

Luwu takes every module's language from its file extension. The command-line tools and the language server agree on it:

ExtensionLanguage
.luwuLuwu
.luauLuau
.luaLua

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.