Repository navigation
Kotlin & method overloading & name mangling, oh my! #525
Description
Activity
Solution 1: We could just remove them from the API binding entirely. This would give us the "same" view as Java does of a Kotlin class.
Solution 2: We could bind them as non-
virtualmethods.We cannot bind "name mangled" methods as C#
virtualmethods, because there's no way to make the subclass scenario work as long as we're using Java as an intermediate language.(We could fix this by using Kotlin as the intermediate language, or by emitting Java byte code directly.)
If we bind the method as a non-
virtualmethod, we can expose the method to C# code, while preventing the subclass scenario. This allows the KotlinExampleBase.foo(UInt)method to be invokable.(We'd still need to know how to name the method so that we avoid the CS0111 errors.)
Solution 2(b): Update
class-parse&generatorso thatgeneratorcan "know" that the method takes aUInt. This would allow us to bindfoo(UInt)asFoo(uint), which can be overloaded, thus avoiding the CS0111 error:namespace Example { public partial class ExampleBase : Java.Lang.Object { [Register ("foo", "(I)V", "GetFoo_IHandler")] public virtual unsafe void Foo (int value) {...} [Register ("foo-WZ4Q5Ns", "(I)V", "GetFoo_IHandler")] public unsafe void Foo (uint value) {...} } }
Reacted by Matthew Leibowitz- added a commit that references this issue
on Nov 22, 2019 I am thinking that Solution 2(b) is actually the most correct solution - we are binding Kotlin here, and it just so happens to use the same IL as Java. All the metadata ins there, so the generator logic is more correct to check for Kotlin data, then the Java data.
Aside (via offline discussion): if we go with 2(b), we'll need to add
JniArgumentValueconstructor overloads for the unsigned types, so that the generated code can compile:[Register ("foo-WZ4Q5Ns", "(I)V", "GetFoo_IHandler")] public unsafe void Foo (uint value) { const string __id = "foo-WZ4Q5Ns.(I)V"; try { JniArgumentValue* __args = stackalloc JniArgumentValue [1]; __args [0] = new JniArgumentValue (value); // hits JniArgumentValue(uint) constructor _members.InstanceMethods.InvokeVirtualVoidMethod (__id, this, __args); } finally { }
Alternatively, we could insert casts:
__args [0] = new JniArgumentValue (unchecked((int) value));
It might be better to add constructor overloads so that any processing is done in the core android as opposed to each library. But then this might cause issues if an old android is used and it does not have these new constructors.
And, I don't think Java is adding new types to their IL any time soon, so potentially breaking for older Android might not be worth it.
Wow, look at my brain wandering. Unless I am wrong, we can't use new constructors as these new libraries won't work on a slightly older Android?
- added a commit that references this issue
on Dec 4, 2019 we can't use new constructors as these new libraries won't work on a slightly older Android?
It took me some thinking to quite understand where you're going, but I think I have it now:
The problem isn't "an old Android version".
The problem is "an old Xamarin.Android version": binding assemblies built against a hypothetical newer Xamarin.Android containing a
JniArgumentValue(uint)constructor -- and using that constructor -- would not be usable on all previous versions of Xamarin.Android.That's a hell of a compat problem.
That reason alone is enough to kill the
JniArgumentValue(uint)constructor overload idea, and necessitate the insertion of casts into the generated source code.Open Questions
What happens with
UIntArray?It looks like
UIntArraybecomes anint[]:package example; open class UnsignedMethods { public open fun uintArray(value: UIntArray) { } }
$ javap -c -s -cp . example.UnsignedMethods Compiled from "hello.kt" public class example.UnsignedMethods { public void uintArray--ajY-9A(int[]); descriptor: ([I)V Code: 0: aload_1 1: ldc #30 // String value 3: invokestatic #36 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: return }It took me some thinking to quite understand where you're going
Sometimes I don't use my words 😑
- added 4 commits that reference this issue
on Jan 7, 2020 - locked and limited conversation to collaborators
on Apr 13, 2024 - added a commit that references this issue
on Jul 1, 2026 - added a commit that references this issue
on Oct 8, 2026
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 bothIntandUIntparameters:The use of a
UIntparameter type causes Kotlin to "name mangle" the method name, generating a method name which is not a valid Java identifier:With PR #505 we can create an API description:
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:Attempting to compile the above will cause a CS0111 error to be generated:
This can be fixed with
Metadata.xml, to "rename" one of the members. (This isn't necessarily ideal, but it's possible.)Were we to do so, we'll find that subclassing won't work:
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 specifyfoo-WZ4Q5Ns. We would thus (presumably; untested) get a Java Callable Wrapper forMyExampleof:This cannot compile.
How do we address this?