Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

Kotlin & method overloading & name mangling, oh my! #525

Description

@jonpryor

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:

packageexample;
publicopenclassExampleBase {
publicopenfunfoo(value:Int) {
}
publicopenfunfoo(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:

<apiapi-source="class-parse">
<packagename="example"jni-name="example">
<classabstract="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">
<constructordeprecated="not deprecated"final="false"name="ExampleBase"static="false"visibility="public"bridge="false"synthetic="false"jni-signature="()V" />
<methodabstract="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">
<parametername="value"type="int"jni-type="I" />
</method>
<methodabstract="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">
<parametername="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:

namespaceExample{[global::Android.Runtime.Register("example/ExampleBase",DoNotGenerateAcw=true)]publicpartialclassExampleBase:Java.Lang.Object{[Register("foo","(I)V","GetFoo_IHandler")]publicvirtualunsafevoidFoo(intvalue){conststring__id="foo.(I)V";try{JniArgumentValue*__args=stackallocJniArgumentValue[1];__args[0]=newJniArgumentValue(value);_members.InstanceMethods.InvokeVirtualVoidMethod(__id,this,__args);}finally{}}[Register("foo-WZ4Q5Ns","(I)V","GetFoo_IHandler")]publicvirtualunsafevoidFoo(intvalue){conststring__id="foo-WZ4Q5Ns.(I)V";try{JniArgumentValue*__args=stackallocJniArgumentValue[1];__args[0]=newJniArgumentValue(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 -->
<attrpath="/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:

classMyExample:Example.ExampleBase{publicoverridevoidFoo2(intvalue){}}

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 */classMyExampleextendsexample.ExampleBase/* ... */ {
publicvoidfoo-WZ4Q5Ns(intvalue) {
n_foo-WZ4Q5Ns (value);
}
nativevoidn_foo-WZ4Q5Ns (intvalue);
}

This cannot compile.

How do we address this?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions