Skip to content

Support Kotlin's unsigned types #539

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

Merged
merged 5 commits into from
Jan 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,16 @@ static string GetPrimitiveClass (Type type)
return "F";
if (type == typeof (int))
return "I";
if (type == typeof (uint))
return "I";
if (type == typeof (long))
return "J";
if (type == typeof (ulong))
return "J";
if (type == typeof (short))
return "S";
if (type == typeof (ushort))
return "S";
if (type == typeof (bool))
return "Z";
return null;
Expand Down
8 changes: 8 additions & 0 deletions src/Java.Interop/Java.Interop/JniArgumentValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public JniArgumentValue (sbyte value)
b = value;
}

public JniArgumentValue (byte value) : this ((sbyte)value) { }

public JniArgumentValue (char value)
{
this = new JniArgumentValue ();
Expand All @@ -46,18 +48,24 @@ public JniArgumentValue (short value)
s = value;
}

public JniArgumentValue (ushort value) : this ((short)value) { }

public JniArgumentValue (int value)
{
this = new JniArgumentValue ();
i = value;
}

public JniArgumentValue (uint value) : this ((int)value) { }

public JniArgumentValue (long value)
{
this = new JniArgumentValue ();
j = value;
}

public JniArgumentValue (ulong value) : this ((long) value) { }

public JniArgumentValue (float value)
{
this = new JniArgumentValue ();
Expand Down
16 changes: 14 additions & 2 deletions src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaTypeReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ public class JavaTypeReference
public static readonly JavaTypeReference Float;
public static readonly JavaTypeReference Double;
public static readonly JavaTypeReference GenericWildcard;

public static readonly JavaTypeReference UInt;
public static readonly JavaTypeReference UShort;
public static readonly JavaTypeReference ULong;
public static readonly JavaTypeReference UByte;

internal static JavaTypeReference GetSpecialType (string name)
{
switch (name) {
Expand All @@ -29,6 +33,10 @@ internal static JavaTypeReference GetSpecialType (string name)
case "long": return Long;
case "float": return Float;
case "double": return Double;
case "uint": return UInt;
case "ushort": return UShort;
case "ulong": return ULong;
case "ubyte": return UByte;
case "?": return GenericWildcard;
}
return null;
Expand All @@ -46,8 +54,12 @@ static JavaTypeReference ()
Float = new JavaTypeReference ("float");
Double = new JavaTypeReference ("double");
GenericWildcard = new JavaTypeReference ("?");
UInt = new JavaTypeReference ("uint");
UShort = new JavaTypeReference ("ushort");
ULong = new JavaTypeReference ("ulong");
UByte = new JavaTypeReference ("ubyte");
}

JavaTypeReference (string specialName)
{
SpecialName = specialName;
Expand Down
1 change: 1 addition & 0 deletions src/Xamarin.Android.Tools.Bytecode/Fields.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public sealed class FieldInfo {
public ConstantPool ConstantPool {get; private set;}
public FieldAccessFlags AccessFlags {get; private set;}
public AttributeCollection Attributes {get; private set;}
public string KotlinType { get; set; }

public FieldInfo (ConstantPool constantPool, Stream stream)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using org.jetbrains.kotlin.metadata.jvm;
using ProtoBuf;
using Type = org.jetbrains.kotlin.metadata.jvm.Type;

namespace Xamarin.Android.Tools.Bytecode
{
// https://github.com/JetBrains/kotlin/blob/master/core/metadata.jvm/src/jvm_metadata.proto
public class KotlinFile
{
public List<KotlinFunction> Functions { get; set; }
Expand Down Expand Up @@ -220,6 +223,8 @@ internal static KotlinExpression FromProtobuf (Expression exp, JvmNameResolver r
public class KotlinFunction : KotlinMethodBase
{
public string Name { get; set; }
public string JvmName { get; set; }
public string JvmSignature { get; set; }
public KotlinFunctionFlags Flags { get; set; }
public KotlinType ReturnType { get; set; }
public int ReturnTypeId { get; set; }
Expand All @@ -235,9 +240,13 @@ internal static KotlinFunction FromProtobuf (Function f, JvmNameResolver resolve
if (f is null)
return null;

var sig = Extensible.GetValue<JvmMethodSignature> (f, 100);

return new KotlinFunction {
Flags = (KotlinFunctionFlags)f.Flags,
Name = resolver.GetString (f.Name),
JvmName = resolver.GetString ((sig?.Name ?? 0) > 0 ? sig.Name : f.Name),
JvmSignature = sig is null ? null : resolver.GetString (sig.Desc),
ReturnType = KotlinType.FromProtobuf (f.ReturnType, resolver),
ReturnTypeId = f.ReturnTypeId,
ReceiverType = KotlinType.FromProtobuf (f.ReceiverType, resolver),
Expand Down
88 changes: 71 additions & 17 deletions src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ public static void Fixup (IList<ClassFile> classes)
FixupJavaMethods (c.Methods);

foreach (var met in metadata.Functions)
FixupFunction (FindJavaMethod (class_metadata, met, c), met, class_metadata);
FixupFunction (FindJavaMethod (metadata, met, c), met, class_metadata);

foreach (var prop in metadata.Properties) {
var getter = FindJavaPropertyGetter (class_metadata, prop, c);
var setter = FindJavaPropertySetter (class_metadata, prop, c);
var getter = FindJavaPropertyGetter (metadata, prop, c);
var setter = FindJavaPropertySetter (metadata, prop, c);

FixupProperty (getter, setter, prop);

FixupField (FindJavaFieldProperty (metadata, prop, c), prop);
}

} catch (Exception ex) {
Expand Down Expand Up @@ -96,7 +98,6 @@ static void FixupConstructor (MethodInfo method, KotlinConstructor metadata)
Log.Debug ($"Kotlin: Hiding internal constructor {method.DeclaringType?.ThisClass.Name.Value} - {metadata.GetSignature ()}");
method.AccessFlags = MethodAccessFlags.Private;
}

}

static void FixupFunction (MethodInfo method, KotlinFunction metadata, KotlinClass kotlinClass)
Expand All @@ -111,18 +112,24 @@ static void FixupFunction (MethodInfo method, KotlinFunction metadata, KotlinCla
return;
}

// Kotlin provides actual parameter names
var java_parameters = method.GetFilteredParameters ();

for (var i = 0; i < java_parameters.Length; i++) {
var java_p = java_parameters [i];
var kotlin_p = metadata.ValueParameters [i];

// Kotlin provides actual parameter names
if (TypesMatch (java_p.Type, kotlin_p.Type, kotlinClass) && java_p.IsUnnamedParameter () && !kotlin_p.IsUnnamedParameter ()) {
Log.Debug ($"Kotlin: Renaming parameter {method.DeclaringType?.ThisClass.Name.Value} - {method.Name} - {java_p.Name} -> {kotlin_p.Name}");
java_p.Name = kotlin_p.Name;
}

// Handle erasure of Kotlin unsigned types
java_p.KotlinType = GetKotlinType (java_p.Type.TypeSignature, kotlin_p.Type.ClassName);
}

// Handle erasure of Kotlin unsigned types
method.KotlinReturnType = GetKotlinType (method.ReturnType.TypeSignature, metadata.ReturnType.ClassName);
}

static void FixupExtensionMethod (MethodInfo method)
Expand Down Expand Up @@ -158,16 +165,32 @@ static void FixupProperty (MethodInfo getter, MethodInfo setter, KotlinProperty
return;
}

// Handle erasure of Kotlin unsigned types
if (getter != null)
getter.KotlinReturnType = GetKotlinType (getter.ReturnType.TypeSignature, metadata.ReturnType.ClassName);

if (setter != null) {
var setter_parameter = setter.GetParameters ().First ();

if (setter_parameter.IsUnnamedParameter ()) {
if (setter_parameter.IsUnnamedParameter () || setter_parameter.Name == "<set-?>") {
Log.Debug ($"Kotlin: Renaming setter parameter {setter.DeclaringType?.ThisClass.Name.Value} - {setter.Name} - {setter_parameter.Name} -> value");
setter_parameter.Name = "value";
}

// Handle erasure of Kotlin unsigned types
setter_parameter.KotlinType = GetKotlinType (setter_parameter.Type.TypeSignature, metadata.ReturnType.ClassName);
}
}

static void FixupField (FieldInfo field, KotlinProperty metadata)
{
if (field is null)
return;

// Handle erasure of Kotlin unsigned types
field.KotlinType = GetKotlinType (field.Descriptor, metadata.ReturnType.ClassName);
}

static MethodInfo FindJavaConstructor (KotlinClass kotlinClass, KotlinConstructor constructor, ClassFile klass)
{
var all_constructors = klass.Methods.Where (method => method.Name == "<init>" || method.Name == "<clinit>");
Expand All @@ -181,16 +204,16 @@ static MethodInfo FindJavaConstructor (KotlinClass kotlinClass, KotlinConstructo
return null;
}

static MethodInfo FindJavaMethod (KotlinClass kotlinClass, KotlinFunction function, ClassFile klass)
static MethodInfo FindJavaMethod (KotlinFile kotlinFile, KotlinFunction function, ClassFile klass)
{
var possible_methods = klass.Methods.Where (method => method.GetMethodNameWithoutSuffix () == function.Name &&
var possible_methods = klass.Methods.Where (method => method.Name == function.JvmName &&
method.GetFilteredParameters ().Length == function.ValueParameters.Count);

foreach (var method in possible_methods) {
if (!TypesMatch (method.ReturnType, function.ReturnType, kotlinClass))
if (!TypesMatch (method.ReturnType, function.ReturnType, kotlinFile))
continue;

if (!ParametersMatch (kotlinClass, method, function.ValueParameters))
if (!ParametersMatch (kotlinFile, method, function.ValueParameters))
continue;

return method;
Expand All @@ -199,7 +222,15 @@ static MethodInfo FindJavaMethod (KotlinClass kotlinClass, KotlinFunction functi
return null;
}

static MethodInfo FindJavaPropertyGetter (KotlinClass kotlinClass, KotlinProperty property, ClassFile klass)
static FieldInfo FindJavaFieldProperty (KotlinFile kotlinClass, KotlinProperty property, ClassFile klass)
{
var possible_methods = klass.Fields.Where (field => field.Name == property.Name &&
TypesMatch (new TypeInfo (field.Descriptor, field.Descriptor), property.ReturnType, kotlinClass));

return possible_methods.FirstOrDefault ();
}

static MethodInfo FindJavaPropertyGetter (KotlinFile kotlinClass, KotlinProperty property, ClassFile klass)
{
var possible_methods = klass.Methods.Where (method => (string.Compare (method.GetMethodNameWithoutSuffix (), $"get{property.Name}", true) == 0 ||
string.Compare (method.GetMethodNameWithoutSuffix (), property.Name, true) == 0) &&
Expand All @@ -209,7 +240,7 @@ static MethodInfo FindJavaPropertyGetter (KotlinClass kotlinClass, KotlinPropert
return possible_methods.FirstOrDefault ();
}

static MethodInfo FindJavaPropertySetter (KotlinClass kotlinClass, KotlinProperty property, ClassFile klass)
static MethodInfo FindJavaPropertySetter (KotlinFile kotlinClass, KotlinProperty property, ClassFile klass)
{
var possible_methods = klass.Methods.Where (method => string.Compare (method.GetMethodNameWithoutSuffix (), $"set{property.Name}", true) == 0 &&
property.ReturnType != null &&
Expand All @@ -219,7 +250,7 @@ static MethodInfo FindJavaPropertySetter (KotlinClass kotlinClass, KotlinPropert
return possible_methods.FirstOrDefault ();
}

static bool ParametersMatch (KotlinClass kotlinClass, MethodInfo method, List<KotlinValueParameter> kotlinParameters)
static bool ParametersMatch (KotlinFile kotlinClass, MethodInfo method, List<KotlinValueParameter> kotlinParameters)
{
var java_parameters = method.GetFilteredParameters ();

Expand All @@ -237,13 +268,13 @@ static bool ParametersMatch (KotlinClass kotlinClass, MethodInfo method, List<Ko
return true;
}

static bool TypesMatch (TypeInfo javaType, KotlinType kotlinType, KotlinClass kotlinClass)
static bool TypesMatch (TypeInfo javaType, KotlinType kotlinType, KotlinFile kotlinFile)
{
// Generic type
if (!string.IsNullOrWhiteSpace (kotlinType.TypeParameterName) && $"T{kotlinType.TypeParameterName};" == javaType.TypeSignature)
return true;

if (javaType.BinaryName == KotlinUtilities.ConvertKotlinTypeSignature (kotlinType, kotlinClass))
if (javaType.BinaryName == KotlinUtilities.ConvertKotlinTypeSignature (kotlinType, kotlinFile))
return true;

// Could be a generic type erasure
Expand All @@ -253,17 +284,40 @@ static bool TypesMatch (TypeInfo javaType, KotlinType kotlinType, KotlinClass ko
// Sometimes Kotlin keeps its native types rather than converting them to Java native types
// ie: "Lkotlin/UShort;" instead of "S"
if (javaType.BinaryName.StartsWith ("L", StringComparison.Ordinal) && javaType.BinaryName.EndsWith (";", StringComparison.Ordinal)) {
if (KotlinUtilities.ConvertKotlinClassToJava (javaType.BinaryName.Substring (1, javaType.BinaryName.Length - 2)) == KotlinUtilities.ConvertKotlinTypeSignature (kotlinType, kotlinClass))
if (KotlinUtilities.ConvertKotlinClassToJava (javaType.BinaryName.Substring (1, javaType.BinaryName.Length - 2)) == KotlinUtilities.ConvertKotlinTypeSignature (kotlinType, kotlinFile))
return true;
}

// Same for some arrays
if (javaType.BinaryName.StartsWith ("[L", StringComparison.Ordinal) && javaType.BinaryName.EndsWith (";", StringComparison.Ordinal)) {
if ("[" + KotlinUtilities.ConvertKotlinClassToJava (javaType.BinaryName.Substring (2, javaType.BinaryName.Length - 3)) == KotlinUtilities.ConvertKotlinTypeSignature (kotlinType, kotlinClass))
if ("[" + KotlinUtilities.ConvertKotlinClassToJava (javaType.BinaryName.Substring (2, javaType.BinaryName.Length - 3)) == KotlinUtilities.ConvertKotlinTypeSignature (kotlinType, kotlinFile))
return true;
}

return false;
}

static string GetKotlinType (string jvmType, string kotlinClass)
{
// Handle erasure of Kotlin unsigned types
if (jvmType == "I" && kotlinClass == "kotlin/UInt;")
return "uint";
if (jvmType == "[I" && kotlinClass == "kotlin/UIntArray;")
Copy link
Contributor

@jonpryor jonpryor Dec 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the "obvious question is obvious" department -- transcending into the "oh god what horrors does this provoke?!" department -- is it possible to be given a [[I or [[[I or...?

There is no kotlin.UIntArrayArray type. However, we "could" do:

	public open fun uintArray(value: Array<UIntArray>) {
	}

...which isn't actually special-cased by the compiler. Meaning it "leaks", with javap showing!

  public void uintArray(kotlin.UIntArray[]);

WTF is a kotlin.UIntArray type? (It's an actual type!)

So in some contexts, UIntArray becomes a JVM int[], and in other contexts it remains a UIntArray.

I'm still not sure how to mentally process this. I'm not sure what this should mean wrt binding at all.

return "uint[]";
if (jvmType == "S" && kotlinClass == "kotlin/UShort;")
return "ushort";
if (jvmType == "[S" && kotlinClass == "kotlin/UShortArray;")
return "ushort[]";
if (jvmType == "J" && kotlinClass == "kotlin/ULong;")
return "ulong";
if (jvmType == "[J" && kotlinClass == "kotlin/ULongArray;")
return "ulong[]";
if (jvmType == "B" && kotlinClass == "kotlin/UByte;")
return "ubyte";
if (jvmType == "[B" && kotlinClass == "kotlin/UByteArray;")
return "ubyte[]";

return null;
}
}
}
4 changes: 2 additions & 2 deletions src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ namespace Xamarin.Android.Tools.Bytecode
{
public static class KotlinUtilities
{
public static string ConvertKotlinTypeSignature (KotlinType type, KotlinClass klass = null)
public static string ConvertKotlinTypeSignature (KotlinType type, KotlinFile metadata = null)
{
if (type is null)
return string.Empty;

var class_name = type.ClassName;

if (string.IsNullOrWhiteSpace (class_name)) {
if (klass is object) {
if (metadata is KotlinClass klass) {

var tp = klass.TypeParameters.FirstOrDefault (t => t.Id == type.TypeParameter);

Expand Down
2 changes: 2 additions & 0 deletions src/Xamarin.Android.Tools.Bytecode/Methods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public sealed class MethodInfo {
public ClassFile DeclaringType {get; private set;}
public MethodAccessFlags AccessFlags {get; set;}
public AttributeCollection Attributes {get; private set;}
public string KotlinReturnType {get; set;}

public MethodInfo (ConstantPool constantPool, ClassFile declaringType, Stream stream)
{
Expand Down Expand Up @@ -290,6 +291,7 @@ public sealed class ParameterInfo : IEquatable<ParameterInfo> {
public string Name;
public int Position;
public TypeInfo Type = new TypeInfo ();
public string KotlinType;

public MethodParameterAccessFlags AccessFlags;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="protobuf-net" Version="2.4.1" />
<PackageReference Include="protobuf-net" Version="2.4.4" />
</ItemGroup>

</Project>
Loading