Mandatory method mangling when compiling to JS


#1

@ilya.gorbunov Thanks for following up.

I understand the annotation proposition.

However, doesn’t the Jetbrains team feel like we shouldn’t have to add an annotation on every method that we intend to consume from JavaScript? Why not allow methods to not be mangled?


JsName annotation in common code for multiplatform projects
#2

The reason for mangling is the ability in Kotlin to overload function by parameter types. E.g. it’s possible to declare foo(x: Int) and foo(x: String) in the same scope, and these are different functions in terms of Kotlin. On a call site of foo compiler checks types and determines which of the functions called (or reports that none of the functions can be called with given parameters). It’s possible in JVM, since in JVM bytecode most of the calls to are encoded with invokevirtual or invokestatic opcodes, which require to specify both name and signature. However, there’s no such thing to JavaScript, so compiler has to invent the way to pass signature. The common technique is to calculate some unique suffix for each set of parameters.

Note that it’s impossible to declare large unmangled function that analyses parameter on run time and calls particular implementation, since run time loses some important information available to compiler. For example:

fun main(args: Array<String>) {
    foo("bar")
    foo("baz" as Any)
}

fun foo(x: String) {
    println("string");
}

fun foo(x: Any) {
    println("any");
}

is impossible to express via runtime resolution. Also, runtime resolution would be very slow, especially in some cases with generics.

Kotlin is not the only language which uses mangling. C++ generates mangled symbols in object files, so it’s impossible to call these functions from pure C. Moreoever, there’s no single standard mangling scheme for C++, so each compiler uses its own mangling algorithm. There’s extern "C" directive in C++ syntax that has effect more or less similar to JsName annotation in Kotlin.


#3

Hi @Alexey.Andreev
Thanks for the detailed answer.

I fully understand why this is needed in JavaScript when facing multiple methods with only the parameters differing between the two.

However, unless I’ve misconfigured something, it appears that the Kotlin to JavaScript compiler always mangles the function names, whether there is a function name conflict or not.

My suggestion would be to only use mangled names when necessary. When facing such a conflict, using @JsName would make more sense than the current situation where you must always provide the annotation if you want to avoid mangled names, whether there’s a conflict or not.

Let me know what you think!


#4

Ok, you create a library foo.1.0.0 which provides fun foo(x: Int) without mangling. User takes this library and calls foo(23) in his application. The generated JS would be straightforward: foo(23). Then you update you library to v1.1.0, which adds fun foo(x: String). Now the compiler has to mangle both functions to resolve the conflict. When the user updates library to the new version, he has to recompile his application to get mangled name on existing call site. You can say: oh, let he recompiles the library and get proper call site. However, that’s usually an issue with transitive dependencies. You may invent 9000 ways to solve problems and I will name 9000 reasons why it won’t work. IMO, the only viable solution here is to invent another annotation that can be applied to some larger scope (i.e. class or file) and that turns off mangling in this entire scope.

You may want to get semless interoperation between Kotlin and JS similar to the experience on JVM between Java and Kotlin (or experience similar to TypeScript and JS). However, looks like it’s impossible. Kotlin was designed primarily as a JVM language and borows many features of JVM languages (like method overloading, lazy class initialization and so forth). TypeScript was designed as JavaScript with type system and borrows JavaScript features (in most cases translation of TS to modern ES is just removal of type annotations). It’s possible to do some relatively good interop between Kotlin and JavaScript or between TypeScript and JVM (if someone manages to implement TypeScript -> JVM bytecode translator), but it would not be 100% seamless.


#5

@Alexey.Andreev those are all great points.

How use case right now is to build a validation library that would be used by

  • Our front end JavaScript (compiled from TypeScript) that runs in the browser
  • Our backend JavaScript services (compiled from TypeScript) that runs on Node.js
  • Our backend JVM services (compiled from Java and Kotlin)

We have a few simple interfaces and a bunch of objects we’d like to share.
Your proposed annotation that would apply to a whole class (perhaps a whole package or module?) would work for us.

In the meantime, we created a custom @Name annotation that maps to @JsName in our Kotlin to JavaScript project. We will use this annotation on every public method of our interface.

Hopefully this is useful feedback for your team. I realize that it is a complex issue! From my perspective, a solution that solves some limited use cases would still be better than the current solution of having mangling unless you add @JsName.

Thanks for your time!


#6

What you can probably get away with (due to the fact that in Javascript a type is merely a convention) is to define your interface as an external interface that you then implement in your class. You are however in that case playing tricks with the typesystem. JsName doesn’t do that, but is more tedious.