Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[generator] Handle Kotlin metadata when only one metadata exists for multiple Java methods. #1009

Merged
merged 1 commit into from
Jul 14, 2022

Conversation

jpobst
Copy link
Contributor

@jpobst jpobst commented Jul 12, 2022

Fixes: #984

There exists a scenario in Kotlin where an instance method and an extension method can have the same Kotlin parameter signature:

class UByteArray {
    fun contains(element: UByte): Boolean
}

// Extension method
operator fun <T> Array<out T>.contains(element: T): Boolean

This results in 2 different Java methods:

class UByteArray {
    public boolean contains-7apg3OU(byte element) {
        return contains-WZ4Q5Ns(this.storage, element);
    }
  
    public static boolean contains-7apg3OU(byte [] arg0, byte element) {
        Intrinsics.checkNotNullParameter(arg0, "arg0");
        return ArraysKt.contains(arg0, element);
    }
}

In this case, Kotlin only generates 1 metadata entry:

image

Today we find that the JvmSignature of ([BB)Z matches the static contains-7apg3OU and apply the metadata to that method.

However, if we ignore the JvmSignature and match on the ValueParameters, then this metadata matches the instance contains-7apg3OU method.

We surmise that this is intentional on Kotlin's part, in order to save bytes by omitting the second metadata entry, and that we should apply this metadata to both Java methods.

Note: the "hash" appended to the Java method name (eg: 7apg3OU) is a short hash of the Kotlin method parameter types.

The Fix

Today we loop through the Kotlin metadata, find the single Java method that matches, and apply the metadata to that match.

In order to support a one-to-many metadata relationship, we need to do the opposite.

We now loop through the Java methods and find the best matching Kotlin metadata entry, if any. (Often there is none.)

In this way, each Java method can consume a matching Kotlin metadata entry, even if another Java method has already matched it.

Results Against kotlin-stdlib 1.6

@@ -4810,7 +4810,7 @@
         jni-signature="(B)Z">
         <parameter
           name="element"
-          type="byte"
+          type="ubyte"
           jni-type="B" />
       </method>
       <method
@@ -5555,7 +5555,7 @@
         jni-signature="(I)Z">
         <parameter
           name="element"
-          type="int"
+          type="uint"
           jni-type="I" />
       </method>
       <method
@@ -6300,7 +6300,7 @@
         jni-signature="(J)Z">
         <parameter
           name="element"
-          type="long"
+          type="ulong"
           jni-type="J" />
       </method>
       <method
@@ -7592,7 +7592,7 @@
         jni-signature="(S)Z">
         <parameter
           name="element"
-          type="short"
+          type="ushort"
           jni-type="S" />
       </method>
       <method

This fixes the 4 contains methods for UIntArray, UByteArray, ULongArray, and UShortArray.

@jpobst jpobst marked this pull request as ready for review July 12, 2022 15:56
@jonpryor
Copy link
Member

[generator] Kotlin metadata can apply to multiple Java method (#1009)

Fixes: https://github.com/xamarin/java.interop/issues/984

There exists a scenario with Kotlin metadata where an instance method
and an extension method can have the same *Kotlin* signature:

	// Kotlin
	class UByteArray {
	    fun contains(element: UByte): Boolean
	}

	// Extension method
	operator fun <T> Array<out T>.contains(element: T): Boolean

This results in 2 different Java methods:

	// "Java"
	class UByteArray {
	    public boolean contains-7apg3OU(byte element) {
	        return contains-WZ4Q5Ns(this.storage, element);
	    }
	
	    public static boolean contains-7apg3OU(byte[] arg0, byte element) {
	        Intrinsics.checkNotNullParameter(arg0, "arg0");
	        return ArraysKt.contains(arg0, element);
	    }
	}

Kotlin only generates 1 metadata entry, for JvmSignature=`([BB)Z`:

![image](https://user-images.githubusercontent.com/179295/178528374-a6e9a568-dc68-4b88-af6b-1c51946d7997.png)

Previously (439bd839) we assumed that a piece of Kotlin metadata
would apply to only a single Java method.  In this case, we would
find `contains-7apg3OU.([BB)Z`, then apply it to only the `static`
method, resulting in a binding of:

	// C#; previous binding
	partial class UByteArray {
	    public bool Contains(int element);
	    public static bool Contains(int[] arg0, uint element);
	}

Note: `element` is of type `int` for the instance method, `uint` for
the static method.  They *should* be consistent, and are not.

If we don't exclusively look at `JvmSignature`, and also check
`ValueParameters`, then the Kotlin metadata will match the *instance*
`contains-7apg3OU` method.

We surmise that this is intentional on Kotlin's part, in order to
save bytes by omitting the second metadata entry, and that we should
apply this metadata to both Java methods.

*Note*: the "hash" appended to the Java method name (eg: `7apg3OU`)
is a short hash of the Kotlin method parameter types.

Fix this scenario by "inverting" our lookup logic: instead of
starting with Kotlin metadata and looking for an applicable Java
method, start with the Java method and look for applicable Kotlin
metadata.  This way, each Java method can consume a matching Kotlin
metadata entry, even if another Java method already matched it.

This allows us to emit a binding of:

	// C#; new binding
	partial class UByteArray {
	    public bool Contains(uint element);
	    public static bool Contains(int[] arg0, uint element);
	}

This fixes the 4 `contains` methods for `UIntArray`, `UByteArray`,
`ULongArray`, and `UShortArray`.

@jonpryor jonpryor merged commit 275fa75 into main Jul 14, 2022
@jonpryor jonpryor deleted the kotlin-metadata-fix branch July 14, 2022 17:53
@github-actions github-actions bot locked and limited conversation to collaborators Apr 12, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Kotlin unsigned type metadata bug
2 participants