Compiler Annotations
To improve translation and compatibility to different Lua interfaces, the TypeScriptToLua transpiler supports several custom annotations that slightly change translation results. This page documents the supported annotations. The syntax of the compiler annotations use the JSDoc syntax.
#
@compileMembersOnlyTarget elements: (declare) enum
This decorator removes an enumeration's name after compilation and only leaves its members. Primarily used for APIs with implicit enumerations.
Example
Playgrounddeclare enum MyEnum { MY_ENUM_MEMBER_A, MY_ENUM_MEMBER_B,}
print(MyEnum.MY_ENUM_MEMBER_A);
print(MyEnum.MY_ENUM_MEMBER_A)
Playground/** @compileMembersOnly */declare enum MyEnum { MY_ENUM_MEMBER_A, MY_ENUM_MEMBER_B,}
print(MyEnum.MY_ENUM_MEMBER_A);
print(MY_ENUM_MEMBER_A)
Example 2
Playgroundenum MyEnum { MY_ENUM_MEMBER_A, MY_ENUM_MEMBER_B, MY_ENUM_MEMBER_C = "c",}
print(MyEnum.MY_ENUM_MEMBER_A);
MyEnum = {}MyEnum.MY_ENUM_MEMBER_A = 0MyEnum.MY_ENUM_MEMBER_B = 1MyEnum.MY_ENUM_MEMBER_C = "c"
print(MyEnum.MY_ENUM_MEMBER_A)
Playground/** @compileMembersOnly */enum MyEnum { MY_ENUM_MEMBER_A, MY_ENUM_MEMBER_B, MY_ENUM_MEMBER_C = "c",}
print(MyEnum.MY_ENUM_MEMBER_A);
MY_ENUM_MEMBER_A = 0MY_ENUM_MEMBER_B = 1MY_ENUM_MEMBER_C = "c"
print(MY_ENUM_MEMBER_A)
#
@customConstructorTarget elements: declare class
Changes the way new instances of this class are made. Takes exactly one argument that is the name of the alternative constructor function.
Example
Playgrounddeclare class MyClass { constructor(x: number);}const inst = new MyClass(3);
local inst = __TS__New(MyClass, 3)
Playground/** @customConstructor MyConstructor */declare class MyClass { constructor(x: number);}const inst = new MyClass(3);
local inst = MyConstructor(3)
#
@noResolutionTarget elements: module
Prevents tstl from trying to resolve the module path. When importing this module the path will be exactly the path in the import statement.
Example
Playgrounddeclare module "mymodule" {}import module from "mymodule";
...local module = require("src.mymodule");
Playground/** @noResolution */declare module "mymodule" {}import module from "mymodule";
...local module = require("mymodule");
#
@noSelfTarget elements: declare class
, (declare) interface
or declare namespace
Indicates that functions inside a scope do not take in initial self
argument when called, and thus will be called with a dot .
instead of a colon :
. It is the same as if each function was declared with an explicit this: void
parameter. Functions that already have an explicit this
parameter will not be affected.
When applied to a class or interface, this only affects the type's declared methods (including static methods and fields with a function type). It will not affect other function declarations, such as nested functions inside a class' methods.
Example
Playgrounddeclare interface NormalInterface { normalMethod(s: string): void;}declare const x: NormalInterface;/** @noSelf **/declare interface NoSelfInterface { noSelfMethod(s: string): void;}declare const y: NoSelfInterface;
x.normalMethod("foo");y.noSelfMethod("bar");
x:normalMethod("foo")y.noSelfMethod("bar")
When applied to a namespace, all functions declared within the namespace will treated as if they do not have a self
parameter. In this case, the effect is recursive, so functions in nested namespaces and types declared as parameters will also be affected.
Example
Playgrounddeclare namespace NormalNS { function normalFunc(s: string): string;}/** @noSelf **/declare namespace NoSelfNS { function noSelfFunc(s: string): string;}
NormalNS.normalFunc("foo");NoSelfNS.noSelfFunc("bar");
NormalNS:normalFunc("foo")NoSelfNS.noSelfFunc("bar")
For more information about how the self
parameter is handled, see Functions and the self
Parameter
#
@noSelfInFileTarget elements: (declare) file
Indicates that functions in a file do not take in initial self
argument when called.
This is annotation works the same as @noSelf being applied to a namespace, but affects the entire file.
@noSelfInFile
must be placed at the top of the file, before the first statement.
#
@tupleReturnTarget elements: (declare) function
This decorator indicates a function returns a lua tuple instead of a table. It influences both destructing assignments of calls of that function, as well as changing the format of returns inside the function body.
Example
Playgroundfunction myFunction(): [number, string] { return [3, "4"];}const [a, b] = myFunction();
function myFunction() return {3, "4"}endlocal a,b = unpack(myFunction())
Playground/** @tupleReturn */function myFunction(): [number, string] { return [3, "4"];}const [a, b] = myFunction();
function myFunction() return 3, "4"endlocal a, b = myFunction()
If you wish to use this annotation on function with overloads, it must be applied to each signature that requires it.
Example
Playground/** @tupleReturn */declare function myFunction(s: string): [string, string];/** @tupleReturn */declare function myFunction(n: number): [number, number];
Note that if any overloaded signature of a function implementation has the annotation, all array/tuple return values will unpacked in the transpiled output.
#
@varargTarget elements: (declare) interface or type
Indicates that an array-like type represents a Lua vararg expression (...
) and should be transpiled to that when used in a spread expression. This is useful for forwarding varargs instead of wrapping them in a table and unpacking them.
Example
Playgroundfunction varargWrapUnpack(...args: string[]) { console.log(...args);}
function varargWrapUnpack(self, ...) local args = ({...}) print(unpack(args))end
Playground/** @vararg */interface Vararg<T> extends Array<T> {}
function varargForward(...args: Vararg<string>) { console.log(...args);}
function varargForward(self, ...) print(...))end
This can be used to access the file-scope varargs as well.
Example
Playgrounddeclare const arg: Vararg<string>;console.log(...arg);const [x, y] = [...arg];
print(...)local x, y = ...
To also support tuple-typed rest parameters, you can define the type like this:
Example
Playground/** @vararg */type Vararg<T extends unknown[]> = T & { __luaVararg?: never };
function varargForward(...args: Vararg<[string, number]>) {}
Warning
TypeScriptToLua does not check that the vararg expression is valid in the context it is used. If the array is used in a spread operation in an invalid context (such as a nested function), a deoptimization will occur.
Example
Playgroundfunction outerFunction(...args: Vararg<string>) { function innerFunction() { console.log(...args); } innerFunction();}
function outerFunction(self, ...) local args = {...} local function innerFunction(self) print(unpack(args)) end innerFunction(_G)end
#
Deprecatedwarning
Some annotations are deprecated and will be/have been removed. Below are the deprecated annotations and instructions to recreate their behavior with vanilla TypeScript.
#
@extensionTarget elements: class
The Extension decorator marks a class as an extension of an already existing class. This causes the class header to not be translated, preventing instantiation and the override of the existing class.
Default Behavior
Playgroundclass MyClass { myFunction(): void {}}
MyClass = __TS__Class()...function MyClass.prototype.myFunction(self) end
Example 1
Playground/** @extension */class MyClass { myFunction(): void {}}
function MyClass.myFunction(self) end
Example 2
Playground/** @extension ExistingClassTable */class MyClass extends ExistingClass { myFunction(): void {}}
function ExistingClassTable.myFunction(self) end
Upgrade Instructions
Use an interface to extend your existing class and declare the table of the existing class as variable.
Example
Playgroundinterface ExistingClass { myFunction(): void;}
declare const ExistingClassTable: ExistingClass;
ExistingClassTable.myFunction = function () {};
function ExistingClassTable.myFunction(self) end
#
@metaExtensionTarget elements: class
The Extension decorator marks a class as an extension of an already existing meta class/table. This causes the class header to not be translated, preventing instantiation and the override of the existing class.
Example
Playgroundclass MyBaseClass { myFunction(): void {}}
MyBaseClass = __TS__Class()...function MyBaseClass.prototype.myFunction(self) end
Playground/** @metaExtension */class MyMetaExtension extends MyMetaClass { myFunction(): void {}}
local __meta__MyMetaClass = debug.getregistry().MyMetaClass__meta__MyMetaClass.myFunction = function(self)end;
Upgrade Instructions
Use an interface to extend your existing class and assign the functions to the meta table of the existing class.
Playgroundinterface MyMetaClass { myFunction(): void;}
const MyMetaClassTable: MyMetaClass = debug.getregistry().MyMetaClass as MyMetaExtension;
MyMetaClassTable.myFunction = function () {};
MyMetaClassTable = debug.getregistry().MyMetaClassMyMetaClassTable.myFunction = function(self)end
#
@phantomTarget elements: namespace
This decorator marks a namespace as a phantom namespace. This means all members of the namespace will be translated as if they were not in that namespace. Primarily used to prevent scoping issues.
Example
Playgroundnamespace myNameSpace { function myFunction(): void {}}
myNameSpace = {}function myNameSpace.myFunction() end
Playground/** @phantom */namespace myNameSpace { function myFunction(): void {}}
function myFunction() end
Upgrade instructions
Use ECMAScript modules and import/export. Alternatively, use a real (non-phantom) namespace.
#
@pureAbstractTarget elements: declare class
This decorator marks a class declaration as purely abstract. The result is that any class extending the purely abstract class will not extend this class in the resulting Lua.
Example
Playgrounddeclare class MyAbstractClass {}class MyClass extends MyAbstractClass {}
MyClass = __TS__Class()MyClass.__base = MyAbstractClassMyClass.____super = MyAbstractClasssetmetatable(MyClass, MyClass.____super)setmetatable(MyClass.prototype, MyClass.____super.prototype)
Playground/** @pureAbstract */declare class MyAbstractClass {}class MyClass extends MyAbstractClass {}
MyClass = __TS__Class()
Upgrade Instructions
Try declaring the "classes" of your lua enviroment as interface. If that is not possible use interface merging as suggested below.
Playgrounddeclare class MyAbstractClass {}interface MyClass extends MyAbstractClass {}
class MyClass {}
MyClass = __TS__Class()
#
@forRangeTarget elements: declare function
Denotes a function declaration is a Lua numerical iterator. When used in a TypeScript for...of
loop, the resulting Lua will use a numerical for loop.
The function should not be a real function and an error will be thrown if it is used in any other way.
Example
Playground/** @forRange */declare function forRange(start: number, limit: number, step?: number): number[];
for (const i of forRange(1, 10)) {}for (const i of forRange(10, 1, -1)) {}
for i = 1, 10 do endfor i = 10, 1, -1 do end
Upgrade Instructions
Use the $range
language extension instead of a custom annotated type.
Playgroundfor (const i of $range(1, 10)) {}for (const i of $range(10, 1, -1)) {}
for i = 1, 10 do endfor i = 10, 1, -1 do end
#
@luaIteratorTarget elements: (declare) interface
Denotes a type is a Lua iterator. When an object of a type with this annotation is used in a for...of statement, it will transpile directly as a lua iterator in a for...in statement, instead of being treated as a TypeScript iterable. Typically, this is used on an interface that extends Iterable
or Array
so that TypeScript will allow it to be used in a for...of statement.
Example
Playground/** @luaIterator */type LuaIterator<T> = Iterable<T>;
declare function myIterator(): LuaIterator<string>;for (const s of myIterator()) {}
for s in myIterator() do end
This can also be combined with @tupleReturn, if the iterator returns multiple values.
Example
Playground/** @luaIterator @tupleReturn */type LuaTupleIterator<T extends any[]> = Iterable<T>;
declare namespace string { function gmatch(s: string, pattern: string): LuaTupleIterator<string[]>;}
for (const [a, b] of string.gmatch("foo", "(.)(.)")) {}
for a, b in string.gmatch("foo", "(.)(.)") do end
Upgrade Instructions
Use the LuaIterable
and LuaMultiReturn
language extensions instead of a custom annotated types.
Playgrounddeclare function myIterator(): LuaIterable<string>;for (const s of myIterator()) {}
for s in myIterator() do end
Playgrounddeclare namespace string { function gmatch(s: string, pattern: string): LuaIterable<LuaMultiReturn<string[]>>;}
for (const [a, b] of string.gmatch("foo", "(.)(.)")) {}
for a, b in string.gmatch("foo", "(.)(.)") do end
#
@luaTableTarget elements: type
This annotation signals the transpiler to translate a class as a simple lua table for optimization purposes.
Playground/** @luaTable */declare class Table<K extends {} = {}, V = any> { readonly length: number; set(key: K, value: V | undefined): void; get(key: K): V | undefined;}
const tbl = new Table(); // local tbl = {}
const foo = {};tbl.set(foo, "bar"); // tbl[foo] = "bar"print(tbl.get(foo)); // print(tbl[foo])
tbl.set(1, "baz"); // tbl[1] = "baz"print(tbl.length); // print(#tbl)
Upgrade Instructions
Use the built-in LuaTable
language extension instead of a custom annotated type.
Playgroundconst tbl = new LuaTable(); // local tbl = {}
const foo = {};tbl.set(foo, "bar"); // tbl[foo] = "bar"print(tbl.get(foo)); // print(tbl[foo])
tbl.set(1, "baz"); // tbl[1] = "baz"print(tbl.length()); // print(#tbl)