Skip to content

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

Closed
@jonpryor

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:

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?

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