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.

@compileMembersOnly#

Target 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

Playground
declare 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

Playground
enum 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 = 0
MyEnum.MY_ENUM_MEMBER_B = 1
MyEnum.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 = 0
MY_ENUM_MEMBER_B = 1
MY_ENUM_MEMBER_C = "c"
print(MY_ENUM_MEMBER_A)

@customConstructor#

Target 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

Playground
declare 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)

@noResolution#

Target 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

Playground
declare module "mymodule" {}
import module from "mymodule";
...
local module = require("src.mymodule");
Playground
/** @noResolution */
declare module "mymodule" {}
import module from "mymodule";
...
local module = require("mymodule");

@noSelf#

Target 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

Playground
declare 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

Playground
declare 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

@noSelfInFile#

Target 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.

@tupleReturn#

Target 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

Playground
function myFunction(): [number, string] {
return [3, "4"];
}
const [a, b] = myFunction();
function myFunction()
return {3, "4"}
end
local a,b = unpack(myFunction())
Playground
/** @tupleReturn */
function myFunction(): [number, string] {
return [3, "4"];
}
const [a, b] = myFunction();
function myFunction()
return 3, "4"
end
local 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.

@vararg#

Target 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

Playground
function 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

Playground
declare 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

Playground
function 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

Deprecated#

warning

Some annotations are deprecated and will be/have been removed. Below are the deprecated annotations and instructions to recreate their behavior with vanilla TypeScript.

@extension#

Deprecated:0.37.0 Removed:TBD

Target 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

Playground
class 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

Playground
interface ExistingClass {
myFunction(): void;
}
declare const ExistingClassTable: ExistingClass;
ExistingClassTable.myFunction = function () {};
function ExistingClassTable.myFunction(self) end

@metaExtension#

Deprecated:0.37.0 Removed:TBD

Target 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

Playground
class 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.

Playground
interface MyMetaClass {
myFunction(): void;
}
const MyMetaClassTable: MyMetaClass = debug.getregistry().MyMetaClass as MyMetaExtension;
MyMetaClassTable.myFunction = function () {};
MyMetaClassTable = debug.getregistry().MyMetaClass
MyMetaClassTable.myFunction = function(self)
end

@phantom#

Deprecated:0.37.0 Removed:TBD

Target 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

Playground
namespace 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.

@pureAbstract#

Deprecated:0.37.0 Removed:TBD

Target 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

Playground
declare class MyAbstractClass {}
class MyClass extends MyAbstractClass {}
MyClass = __TS__Class()
MyClass.__base = MyAbstractClass
MyClass.____super = MyAbstractClass
setmetatable(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.

Playground
declare class MyAbstractClass {}
interface MyClass extends MyAbstractClass {}
class MyClass {}
MyClass = __TS__Class()

@forRange#

Deprecated:0.38.0 Removed:TBD

Target 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 end
for i = 10, 1, -1 do end

Upgrade Instructions

Use the $range language extension instead of a custom annotated type.

Playground
for (const i of $range(1, 10)) {}
for (const i of $range(10, 1, -1)) {}
for i = 1, 10 do end
for i = 10, 1, -1 do end

@luaIterator#

Deprecated:0.38.1 Removed:TBD

Target 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.

Playground
declare function myIterator(): LuaIterable<string>;
for (const s of myIterator()) {}
for s in myIterator() do end
Playground
declare 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

@luaTable#

Deprecated:0.39.0 Removed:TBD

Target 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.

Playground
const 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)