Advanced Features & Interop¶
This section covers Kotlin's null-safety, operator overloading, infix functions, scope functions, collections, and specific interop features for environments like Dota 2.
Null Safety¶
One of the most powerful features of Kotlin is its null-safety. ktox-lua transpiles Kotlin's null-safety operators (?., ?:, !!) into robust, multi-step Lua checks.
Safe Call Operator (?.)¶
Each step in a safe call chain is transpiled into a do...end block with temporary variables to ensure the receiver is evaluated only once.
Kotlin:
Lua:
local result
do
local __t0 = calc
local __t1 = (__t0 ~= nil) and __t0:add(3, 7) or nil
result = __t1
end
Example of a longer chain: Kotlin:
Lua:
local result
do
local __t0 = aaa
local __t1 = (__t0 ~= nil) and __t0.bbb or nil
local __t2 = (__t1 ~= nil) and ktox_replace(__t1, "a", "b") or nil
if __t2 ~= nil then
result = __t2
else
result = "nothing!"
end
end
Elvis Operator (?:)¶
The Elvis operator provides a default value if the preceding expression evaluated to nil.
Kotlin:
Lua:
local result
do
local __t0 = calc
local __t1 = (__t0 ~= nil) and __t0:add(3, 7) or nil
if __t1 ~= nil then
result = __t1
else
result = -1
end
end
Not-Null Assertion (!!)¶
Kotlin's !! operator is supported by direct access.
Kotlin:
Lua:
Smart Casts and is checks¶
ktox-lua supports type checks using the is and !is operators.
Kotlin:
Lua:
if ktox_isinstance(animal, Dog) then
animal:bark()
end
if type(name) == "string" then
print(#name)
end
ktox_isinstance is a helper function provided by ktox-lib.lua for user-defined classes, while primitive types use Lua's built-in type() function.)
Operators & Infix Functions¶
ktox-lua supports Kotlin's operator overloading and infix functions, mapping them to Lua's metamethods or helper functions.
Operator Overloading¶
Many Kotlin operators are transpiled directly to Lua's metamethods.
| Kotlin Operator | Lua Metamethod | Method Name |
|---|---|---|
a + b |
__add |
plus |
a - b |
__sub |
minus |
a * b |
__mul |
times |
a / b |
__div |
div |
-a |
__unm |
unaryMinus |
a == b |
__eq |
equals |
a < b, a <= b |
__lt, __le |
compareTo |
a(args) |
__call |
invoke |
toString() |
__tostring |
toString |
Note: Operators without direct Lua metamethods (like inc, dec, get, set, contains) are transpiled into explicit method calls or ktox_ helper calls.
Example:
class Vec2(val x: Int, val y: Int) {
operator fun plus(other: Vec2): Vec2 = Vec2(this.x + other.x, this.y + other.y)
override fun toString(): String = "(${this.x}, ${this.y})"
}
val a = Vec2(3, 4)
val b = Vec2(1, 2)
val c = a + b
println(c)
Lua:
function Vec2:plus(other)
return Vec2:new(self.x + other.x, self.y + other.y)
end
Vec2.__add = function(a, b) return a:plus(b) end
function Vec2:toString()
return "(" .. tostring(self.x) .. ", " .. tostring(self.y) .. ")"
end
Vec2.__tostring = function(a) return a:toString() end
Extension Functions¶
Extension functions are transpiled to top-level functions (for primitive types) or class methods (for user-defined classes).
Kotlin (Top-level):
Lua:
Kotlin (Class extension):
Lua:
Compound Assignments¶
Compound assignments (+=, -=, etc.) for collections are transpiled to ktox_ helper calls to ensure correct mutations.
Kotlin:
Lua:
Collections & Iterables¶
ktox-lua provides a comprehensive suite of collection functions transpiled into efficient Lua helpers in ktox-lib.lua. Kotlin lists and maps are both represented as Lua tables.
Lists and Maps¶
Kotlin:
Lua:
Common Operations¶
| Operation | Kotlin Example | Lua Transpilation |
|---|---|---|
| Iteration | numbers.forEach { ... } |
ktox_forEach(numbers, ...) |
| Transformation | numbers.map { ... } |
ktox_map(numbers, ...) |
list.flatten() |
ktox_flatten(list) |
|
| Filtering | numbers.filter { ... } |
ktox_filter(numbers, ...) |
| Search | numbers.find { ... } |
ktox_find(numbers, ...) |
| Predicates | numbers.any { ... } |
ktox_any(numbers, ...) |
| Sorting | numbers.sortedBy { it.age } |
ktox_sortedBy(numbers, ...) |
| Aggregation | numbers.fold(0) { ... } |
ktox_fold(numbers, ...) |
numbers.joinToString(", ") |
ktox_joinToString(numbers, ", ") |
|
| Properties | list.size |
#list |
string.length |
#string |
|
map.keys |
ktox_keys(map) |
|
pair.first |
pair[1] |
Scope Functions¶
Kotlin's scope functions (let, run, with, apply, and also) are implemented as Lua helpers.
| Function | Receiver Reference | Return Value | Transpiled Helper |
|---|---|---|---|
let |
it |
Lambda result | ktox_let |
run |
this |
Lambda result | ktox_run |
with |
this |
Lambda result | ktox_with |
apply |
this |
Receiver object | ktox_apply |
also |
it |
Receiver object | ktox_also |
Example (apply):
Lua:
Exceptions¶
Kotlin's exception handling uses throw (Lua error()) and try/catch/finally (Lua pcall()/xpcall()).
Kotlin:
Lua:
local __ok, __err = pcall(function()
error(RuntimeException:new("fail"))
end)
if not __ok then
local e = __err
print("Caught: " .. tostring(e))
end
Annotations & Interop¶
ktox annotations map idiomatic Kotlin declarations onto an existing native API — typically declared once in a types library and consumed as a jar, so application code stays pure Kotlin.
@NativeName¶
Maps a declaration to its native name. Where the annotation sits decides the emitted SHAPE:
Function rename — calls emit the native name:
@NativeName("FindUnitsInRadius")
fun findUnitsInRadius(origin: Vector, radius: Float): List<BaseNPC> = externalSource()
Accessor property (@get: / @set:) — reads and writes become native METHOD CALLS:
local hp = unit:GetHealth()
unit:SetHealth(hp + 50)
unit:SetHealth(unit:GetHealth() + 50) -- compound assignment reads then writes
Bare property rename — a plain @NativeName on a property is a FIELD rename (reads and
writes stay field accesses); a dotted name (@NativeName("style.clip")) renames onto a nested
path. Mixing is fine — each side resolves independently (@get:NativeName("GetMana") +
@NativeName("mana_field") reads via the call, writes the field).
Resolution is receiver-type-aware: the annotation is looked up on the RECEIVER's type (source or
compiled jar, following supertypes), so two types can map the same Kotlin name to different native
names, and a plain data class field never borrows an unrelated type's accessor. Enum classes and
entries rename the same way.
External data classes¶
Constructing a data class that exists ONLY in a types jar (no transpiled output — e.g. an engine
event payload) emits a plain table, because there is no Lua class to instantiate:
Data classes that ARE part of the program (your own modules, shared modules) construct normally.
Value classes¶
A @JvmInline value class is an identity at runtime — construction and .value reads vanish:
@ReplaceReferencesWithLiteral¶
Replaces every reference to a property/function with a literal string. Useful for event names or dictionary keys.
Source Maps¶
ktox-lua generates source maps for easier debugging. When an error occurs in Lua, the traceback is rewritten to point to the original Kotlin source file and line number.