Description
Context: #505
PR #505 improves support for binding certain Kotlin constructs.
In "the perfect is the enemy of the good" manner, we will be merging PR #505 even though there's a known issue with it, in part because we don't currently know how to fix this scenario:
Method overloads and "name mangling".
In various situations, Kotlin will emit member names which are not valid Java identifiers. For example, consider this Kotlin class, which overloads the ExampleBase.foo()
method to accept both Int
and UInt
parameters:
package example;
public open class ExampleBase {
public open fun foo(value : Int) {
}
public open fun foo(value : UInt) {
}
}
The use of a UInt
parameter type causes Kotlin to "name mangle" the method name, generating a method name which is not a valid Java identifier:
$ kotlinc hello.kt
$ javap -cp . example.ExampleBase
Compiled from "hello.kt"
public class example.ExampleBase {
public void foo(int);
public void foo-WZ4Q5Ns(int);
public example.ExampleBase();
}
With PR #505 we can create an API description:
<api
api-source="class-parse">
<package
name="example"
jni-name="example">
<class
abstract="false"
deprecated="not deprecated"
jni-extends="Ljava/lang/Object;"
extends="java.lang.Object"
extends-generic-aware="java.lang.Object"
final="false"
name="ExampleBase"
jni-signature="Lexample/ExampleBase;"
source-file-name="hello.kt"
static="false"
visibility="public">
<constructor
deprecated="not deprecated"
final="false"
name="ExampleBase"
static="false"
visibility="public"
bridge="false"
synthetic="false"
jni-signature="()V" />
<method
abstract="false"
deprecated="not deprecated"
final="false"
name="foo"
native="false"
return="void"
jni-return="V"
static="false"
synchronized="false"
visibility="public"
bridge="false"
synthetic="false"
jni-signature="(I)V">
<parameter
name="value"
type="int"
jni-type="I" />
</method>
<method
abstract="false"
deprecated="not deprecated"
final="false"
name="foo-WZ4Q5Ns"
native="false"
return="void"
jni-return="V"
static="false"
synchronized="false"
visibility="public"
bridge="false"
synthetic="false"
jni-signature="(I)V">
<parameter
name="value"
type="int"
jni-type="I" />
</method>
</class>
</package>
</api>
The problem comes when we attempt to generate C# source to bind the above API description. It cannot compile, because ExampleBase.Foo(int)
is emitted twice:
namespace Example {
[global::Android.Runtime.Register ("example/ExampleBase", DoNotGenerateAcw=true)]
public partial class ExampleBase : Java.Lang.Object {
[Register ("foo", "(I)V", "GetFoo_IHandler")]
public virtual unsafe void Foo (int value)
{
const string __id = "foo.(I)V";
try {
JniArgumentValue* __args = stackalloc JniArgumentValue [1];
__args [0] = new JniArgumentValue (value);
_members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args);
} finally {
}
}
[Register ("foo-WZ4Q5Ns", "(I)V", "GetFoo_IHandler")]
public virtual unsafe void Foo (int value)
{
const string __id = "foo-WZ4Q5Ns.(I)V";
try {
JniArgumentValue* __args = stackalloc JniArgumentValue [1];
__args [0] = new JniArgumentValue (value);
_members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args);
} finally {
}
}
}
}
Attempting to compile the above will cause a CS0111 error to be generated:
src/Example.ExampleBase.cs(98,30): error CS0111: Type 'ExampleBase' already defines a member called 'Foo' with the same parameter types
This can be fixed with Metadata.xml
, to "rename" one of the members. (This isn't necessarily ideal, but it's possible.)
<!-- Untested, but go with it for expository purposes -->
<attr path="/api/package[@name='example']/class[@name='ExampleBase']/method[@name='foo-WZ4Q5Ns']" name="managedName">Foo2</attr>
Were we to do so, we'll find that subclassing won't work:
class MyExample : Example.ExampleBase {
public override void Foo2 (int value) {}
}
Subclassing cannot work, because we use Java as an intermediary, and the Java intermediary -- as generated by src/Java.Interop.Tools.JavaCallableWrappers
-- uses the [Register]
attribute, which will still specify foo-WZ4Q5Ns
. We would thus (presumably; untested) get a Java Callable Wrapper for MyExample
of:
/* partial */ class MyExample extends example.ExampleBase /* ... */ {
public void foo-WZ4Q5Ns(int value) {
n_foo-WZ4Q5Ns (value);
}
native void n_foo-WZ4Q5Ns (int value);
}
This cannot compile.
How do we address this?