Skip to content

Known limitations

A transpiler's contract is: produce correct output, or fail loudly. Silently emitting plausible-but-wrong code is the one unacceptable outcome. The cases below are where ktox does not yet meet that contract. They are grouped by how they surface today.

Legend:

  • 🔴 Silent miscompilation — ktox emits output that transpiles and passes the output check (checkLuaOutput load / checkJsOutput node --check / checkCppOutput -fsyntax-only) but is wrong at runtime. These are the priority gaps: a user gets no signal.
  • 🟠 Invalid output — ktox emits target code that does not load/compile. The output check catches it (a clear error), but the transpiler itself does not reject the input up front.
  • 🟢 Rejected — ktox throws KtoxUnsupportedException with a clickable location. Working as intended; listed so the boundary is explicit.

🔴 Silent miscompilation

vararg constructor parameter — trailing elements dropped

class VP(val name: String, vararg val items: Int)
VP("a", 1, 2, 3)

A constructor emits its signature with the vararg listed by name (function VP:new(name, items)), not as ..., so the call VP:new("a", 1, 2, 3) binds items = 1 and discards 2, 3 — and items is a scalar, not the expected array/table. The output loads cleanly, so no check fires.

  • Backends: Lua (verified); JS/C++ constructors share the shape.
  • Workaround: in a constructor take items: List<Int> (or IntArray) instead of vararg.
  • Note: a function vararg (fun f(vararg xs: Int)) called positionally is handled correctly; only the constructor path and the vararg-plus-named case below are affected.

Non-literal parameter default — emitted verbatim (JS, C++)

fun f(a: Int, xs: List<Int> = listOf(1, 2))
f(0)   // default fires

JS and C++ carry a parameter default into the emitted signature as the raw Kotlin default text: function f(a, xs = listOf(1, 2)). listOf is not a JS/C++ symbol, so the default throws ReferenceError (JS) / fails to compile (C++). In JS the emitted default is syntactically valid, so node --check passes — the failure only appears at runtime when the default is actually used.

  • Backends: JS and C++. Lua is correct here — it transpiles the default expression (if xs == nil then xs = {1, 2} end), not raw text.
  • Workaround: give the parameter a default that is a literal in the target language, or always pass the argument explicitly.

vararg followed by another parameter (named-only) — argument misplaced

fun f(vararg xs: Int, name: String = "")
f(1, 2, name = "n")

A parameter after a vararg can only be supplied by name. Argument reordering writes the positional vararg element into the trailing parameter's slot and then overwrites it with the named argument, so a vararg element is silently lost (f(1, "n") — the 2 is gone).

  • Backends: core argument reordering (all). In Lua the emitted function f(..., name) is additionally invalid (... must be last) and so fails checkLuaOutput; JS/C++ surface only the silent call-site misplacement.
  • Workaround: do not declare parameters after a vararg.

Named arguments on a non-Kotlin classpath class — collapse positionally

// SomeJavaType comes from a plain Java jar compiled without -parameters
SomeJavaType(id = 1, label = "x")

Parameter names and default-presence for a jar-consumed class come from Kotlin @Metadata. A plain Java class has none, so java reflection yields arg0, arg1, …; a named argument matches nothing and is appended positionally. (Kotlin jar classes — the normal ktox dependency — are handled correctly.)

  • Backends: core resolution (all).
  • Workaround: pass arguments positionally, or compile the Java dependency with -parameters.

🟠 Invalid output (caught by the output check, not by the transpiler)

C++ parameter default that references another parameter

fun mk(a: Int, b: Int = a * 2): Int = a + b

Emits int32_t mk(int32_t a, int32_t b = a * 2). A C++ default argument may not reference another parameter, so the signature is ill-formed and fails checkCppOutput / the user's compiler. This is emitted even when the default is used normally (mk(3)). (Skipping b at a call site is instead rejected up front — see below.)

  • Backend: C++.
  • Workaround: make the default a self-contained constant, or always pass the argument.

C++ classpath default typed as a class with no default constructor

A skipped default for a classpath callee value-initializes with {} (its true default is not in source). {} is well-formed for scalars, std::string, std::optional, aggregates, and classes with a default constructor — but ill-formed for a class type that has only a user-declared non-default constructor, so it fails at C++ compile.

  • Backend: C++.
  • Workaround: pass that argument explicitly.

🟢 Rejected up front (KtoxUnsupportedException)

These inputs cannot be lowered correctly and are refused with a located error rather than miscompiled.

  • Lua constructor default / super-call argument that references an instance member — a member-function call or a non-parameter @get:NativeName getter. self does not exist yet at that point, so self:Foo() would run on the class table. (A sibling constructor parameter, including a @get:NativeName one, is fine — it renders as the bare parameter.)
  • C++ call site that skips a parameter whose default references another parameter — the default cannot be reproduced in the caller's scope, so the argument must be passed explicitly.