From 1c0d7a5a6d0a5b39648c0c2eeb84ac6990c33fb7 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Mon, 6 Jan 2020 21:28:36 -0600 Subject: [PATCH] [generator] Support Kotlin's unsigned types (#539) Fixes: https://github.com/xamarin/java.interop/issues/525 Context: https://github.com/xamarin/xamarin-android/pull/4054 Context: https://github.com/Kotlin/KEEP/blob/13b67668ccc5b4741ecc37d0dd050fd77227c035/proposals/unsigned-types.md Context: https://kotlinlang.org/docs/reference/basic-types.html#unsigned-integers Another place where Kotlin makes use of "name mangling" -- see also commit f3553f41 -- is in the use of unsigned types such as `UInt`. At the JVM ABI level, Kotlin treats unsigned types as their signed counterparts, e.g. `kotlin.UInt` is an `int` and `kotlin.UIntArray` is an `int[]`: // Kotlin public class Example { public fun value(value: UInt) : UInt { return value } public fun array(value: UIntArray) : UIntArray { return value } } // `javap` output: public final class Example { public final int value-WZ4Q5Ns(int); public final int[] array--ajY-9A(int[]); } Kotlin uses Java Annotations to determine whether a parameter or return type is actually an unsigned type instead of a signed type. Update `Xamarin.Android.Tools.Bytecode` and `generator` to bind e.g.: * `kotlin.UInt` as `System.UInt32` * `kotlin.UIntArray` as a `System.UInt32[]` and likewise for the other unsigned types `ushort`, `ulong`, `ubyte`. In order to do this, we pretend that they are native Java types and just translate a few places where we need to tell Java the real type. ~~ Xamarin.Android.Tools.Bytecode / class-parse ~~ When we read the Kotlin metadata in the Java bytecode, if we come across one of these types we store it within an additional `FieldInfo.KotlinType` property that we can access later. When we are generating the XML we check this additional flag and if it's one of our types we emit it instead of the native Java type. For example: Here we see that even though `@jni-return` is `I` -- meaning `int` -- the `@return` property is `uint`. Likewise `parameter/@jni-type` and `parameter/@type`. The JNI ABI is `int`, but we bind in C# as `uint`. ~~ ApiXmlAdjuster ~~ Update `JavaTypeReference` to contain unsigned types: UInt = new JavaTypeReference ("uint"); UShort = new JavaTypeReference ("ushort"); ULong = new JavaTypeReference ("ulong"); UByte = new JavaTypeReference ("ubyte"); ~~ generator ~~ `generator` has the 4 new types added to the `SymbolTable` as `SimpleSymbols`: AddType (new SimpleSymbol ("0", "uint", "uint", "I", returnCast: "(uint)")); AddType (new SimpleSymbol ("0", "ushort", "ushort", "S", returnCast: "(ushort)")); AddType (new SimpleSymbol ("0", "ulong", "ulong", "J", returnCast: "(ulong)")); AddType (new SimpleSymbol ("0", "ubyte", "byte", "B", returnCast: "(byte)")); There are 2 fixups we have to make because we use `GetIntValue(...)`, etc. instead of having unsigned versions: * Override name of which method to call, e.g.: `GetIntValue()` instead of `GetUintValue()`. * Cast the `int` value returned to `uint`. This is accomplished via the new `ISymbol.ReturnCast` property. ~~ A Note On API Compatibility ~~ Bindings which use Kotlin Unsigned Types will *only* work on Xamarin.Android 10.2.0 or later ("Visual Studio 16.5"). The problem is that while we *can* emit C# source code which will *compile* against older versions of Xamarin.Android, if they use arrays they will not *run* under older versions of Xamarin.Android. For example, imagine this binding code for the above Kotlin `Example.array()` method: // C# Binding of Example.array() partial class Example { public unsafe uint[] Array(uint[] value) { const string __id = "array--ajY-9A.([I)[I"; IntPtr native_value = JNIEnv.NewArray ((int[]) (object) value); // Works!...ish? JniArgumentValue* __args = stackalloc JniArgumentValue [1]; __args [0] = new JniArgumentValue (native_value); JniObjectReference r = _members.InstanceMethods.InvokeVirtualIntMethod (__id, this, __args); return (uint[]) JNIEnv.GetArray (r.Handle, JniHandleOwnership.DoNotTransfer, typeof (uint)); } } That could conceivably *compile* against older Xamarin.Android versions. However, that cannot *run* against older Xamarin.Android versions, as *eventually* `JNIEnv.GetArray()` will hit some dictionaries to determine how to marshal `IntPtr` to a `uint[]`, at which point things will fail because there is no such mapping *until* Xamarin.Android 10.2.0. We feel that a "hard" ABI requirement will have more "graceful" failure conditions than a solution which doesn't add ABI requirements. In this case, if you create a Kotlin library binding which exposes unsigned types, attempting to build an app in Release configuration against older Xamarin.Android versions will result in a linker error, as the required `JNIEnv` methods will not be resolvable. (cherry picked from commit 71afce55812fc2c5efd6002580b539d8394f7bca) --- .../JavaNativeTypeManager.cs | 6 + .../Java.Interop/JniArgumentValue.cs | 8 ++ .../JavaTypeReference.cs | 16 ++- src/Xamarin.Android.Tools.Bytecode/Fields.cs | 1 + .../Kotlin/KotlinClassMetadata.cs | 9 ++ .../Kotlin/KotlinFixups.cs | 88 +++++++++--- .../Kotlin/KotlinUtilities.cs | 4 +- src/Xamarin.Android.Tools.Bytecode/Methods.cs | 2 + .../Tests/KotlinFixupsTests.cs | 117 ++++++++++++++++ .../Tests/kotlin/UnsignedTypes.class | Bin 0 -> 2972 bytes .../Tests/kotlin/UnsignedTypes.kt | 28 ++++ .../Tests/kotlin/UnsignedTypesKt.class | Bin 0 -> 1101 bytes .../XmlClassDeclarationBuilder.cs | 9 +- .../CodeGenerator.cs | 2 +- .../JavaInteropCodeGenerator.cs | 16 ++- .../Field.cs | 2 +- .../GenBase.cs | 2 + .../Method.cs | 2 +- .../MethodBase.cs | 5 +- .../ReturnValue.cs | 11 +- .../Symbols/ArraySymbol.cs | 19 ++- .../Symbols/CharSequenceSymbol.cs | 2 + .../Symbols/CollectionSymbol.cs | 2 + .../Symbols/EnumSymbol.cs | 2 + .../Symbols/FormatSymbol.cs | 6 +- .../Symbols/GeneratedEnumSymbol.cs | 2 + .../Symbols/GenericSymbol.cs | 2 + .../Symbols/GenericTypeParameter.cs | 2 + .../Symbols/ISymbol.cs | 1 + .../Symbols/SimpleSymbol.cs | 4 +- .../Symbols/StreamSymbol.cs | 2 + .../Symbols/StringSymbol.cs | 2 + .../Symbols/SymbolTable.cs | 8 ++ .../Symbols/XmlPullParserSymbol.cs | 2 + .../Symbols/XmlResourceParserSymbol.cs | 2 + .../KotlinFixups.cs | 33 +++-- ...iteKotlinUnsignedArrayTypeMethodsClass.txt | 90 ++++++++++++ ...KotlinUnsignedArrayTypePropertiesClass.txt | 130 ++++++++++++++++++ .../WriteKotlinUnsignedTypeMethodsClass.txt | 70 ++++++++++ ...WriteKotlinUnsignedTypePropertiesClass.txt | 110 +++++++++++++++ .../Tests/Unit-Tests/CodeGeneratorTests.cs | 88 ++++++++++++ .../Tests/Unit-Tests/KotlinFixupsTests.cs | 2 +- tools/generator/Tests/generator-Tests.csproj | 12 ++ .../generator/Utilities/TypeNameUtilities.cs | 23 ++++ 44 files changed, 888 insertions(+), 56 deletions(-) create mode 100644 src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/UnsignedTypes.class create mode 100644 src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/UnsignedTypes.kt create mode 100644 src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/UnsignedTypesKt.class create mode 100644 tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedArrayTypeMethodsClass.txt create mode 100644 tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedArrayTypePropertiesClass.txt create mode 100644 tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedTypeMethodsClass.txt create mode 100644 tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedTypePropertiesClass.txt diff --git a/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs b/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs index 41ea7cd22..97c34f0b1 100644 --- a/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs +++ b/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs @@ -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; diff --git a/src/Java.Interop/Java.Interop/JniArgumentValue.cs b/src/Java.Interop/Java.Interop/JniArgumentValue.cs index 6f6ee2fb5..8ea9257a1 100644 --- a/src/Java.Interop/Java.Interop/JniArgumentValue.cs +++ b/src/Java.Interop/Java.Interop/JniArgumentValue.cs @@ -34,6 +34,8 @@ public JniArgumentValue (sbyte value) b = value; } + public JniArgumentValue (byte value) : this ((sbyte)value) { } + public JniArgumentValue (char value) { this = new JniArgumentValue (); @@ -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 (); diff --git a/src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaTypeReference.cs b/src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaTypeReference.cs index 61cd2dbca..877b7d58b 100644 --- a/src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaTypeReference.cs +++ b/src/Xamarin.Android.Tools.ApiXmlAdjuster/JavaTypeReference.cs @@ -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) { @@ -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; @@ -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; diff --git a/src/Xamarin.Android.Tools.Bytecode/Fields.cs b/src/Xamarin.Android.Tools.Bytecode/Fields.cs index df316e3ac..28f9685e1 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Fields.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Fields.cs @@ -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) { diff --git a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinClassMetadata.cs b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinClassMetadata.cs index 901f5f8f5..1b395fecb 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinClassMetadata.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinClassMetadata.cs @@ -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 Functions { get; set; } @@ -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; } @@ -235,9 +240,13 @@ internal static KotlinFunction FromProtobuf (Function f, JvmNameResolver resolve if (f is null) return null; + var sig = Extensible.GetValue (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), diff --git a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs index c205c06e0..edc893233 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs @@ -45,13 +45,15 @@ public static void Fixup (IList 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) { @@ -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) @@ -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) @@ -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 == "") { 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 == "" || method.Name == ""); @@ -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; @@ -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) && @@ -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 && @@ -219,7 +250,7 @@ static MethodInfo FindJavaPropertySetter (KotlinClass kotlinClass, KotlinPropert return possible_methods.FirstOrDefault (); } - static bool ParametersMatch (KotlinClass kotlinClass, MethodInfo method, List kotlinParameters) + static bool ParametersMatch (KotlinFile kotlinClass, MethodInfo method, List kotlinParameters) { var java_parameters = method.GetFilteredParameters (); @@ -237,13 +268,13 @@ static bool ParametersMatch (KotlinClass kotlinClass, MethodInfo method, List t.Id == type.TypeParameter); diff --git a/src/Xamarin.Android.Tools.Bytecode/Methods.cs b/src/Xamarin.Android.Tools.Bytecode/Methods.cs index 187138ef9..3add9fcdd 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Methods.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Methods.cs @@ -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) { @@ -290,6 +291,7 @@ public sealed class ParameterInfo : IEquatable { public string Name; public int Position; public TypeInfo Type = new TypeInfo (); + public string KotlinType; public MethodParameterAccessFlags AccessFlags; diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/KotlinFixupsTests.cs b/src/Xamarin.Android.Tools.Bytecode/Tests/KotlinFixupsTests.cs index 2da9a3d35..dcfb774da 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Tests/KotlinFixupsTests.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/KotlinFixupsTests.cs @@ -119,5 +119,122 @@ public void RenameSetterParameter () Assert.AreEqual ("value", p.Name); } + + [Test] + public void UnsignedMethods () + { + var klass = LoadClassFile ("UnsignedTypes.class"); + + var uint_method = klass.Methods.Single (m => m.Name.Contains ("foo_uint-")); + var ushort_method = klass.Methods.Single (m => m.Name.Contains ("foo_ushort-")); + var ulong_method = klass.Methods.Single (m => m.Name.Contains ("foo_ulong-")); + var ubyte_method = klass.Methods.Single (m => m.Name.Contains ("foo_ubyte-")); + var uintarray_method = klass.Methods.Single (m => m.Name.Contains ("foo_uintarray-")); + var ushortarray_method = klass.Methods.Single (m => m.Name.Contains ("foo_ushortarray-")); + var ulongarray_method = klass.Methods.Single (m => m.Name.Contains ("foo_ulongarray-")); + var ubytearray_method = klass.Methods.Single (m => m.Name.Contains ("foo_ubytearray-")); + var uintarrayarray_method = klass.Methods.Single (m => m.Name.Contains ("foo_uintarrayarray")); + + KotlinFixups.Fixup (new [] { klass }); + + Assert.AreEqual ("uint", uint_method.GetParameters () [0].KotlinType); + Assert.AreEqual ("uint", uint_method.KotlinReturnType); + + Assert.AreEqual ("ushort", ushort_method.GetParameters () [0].KotlinType); + Assert.AreEqual ("ushort", ushort_method.KotlinReturnType); + + Assert.AreEqual ("ulong", ulong_method.GetParameters () [0].KotlinType); + Assert.AreEqual ("ulong", ulong_method.KotlinReturnType); + + Assert.AreEqual ("ubyte", ubyte_method.GetParameters () [0].KotlinType); + Assert.AreEqual ("ubyte", ubyte_method.KotlinReturnType); + + Assert.AreEqual ("uint[]", uintarray_method.GetParameters () [0].KotlinType); + Assert.AreEqual ("uint[]", uintarray_method.KotlinReturnType); + + Assert.AreEqual ("ushort[]", ushortarray_method.GetParameters () [0].KotlinType); + Assert.AreEqual ("ushort[]", ushortarray_method.KotlinReturnType); + + Assert.AreEqual ("ulong[]", ulongarray_method.GetParameters () [0].KotlinType); + Assert.AreEqual ("ulong[]", ulongarray_method.KotlinReturnType); + + Assert.AreEqual ("ubyte[]", ubytearray_method.GetParameters () [0].KotlinType); + Assert.AreEqual ("ubyte[]", ubytearray_method.KotlinReturnType); + + // Kotlin's Array does not trigger this code because it is not + // encoded as Java's "[[I", instead it is exposed as "UIntArray[]", so + // we treat it as a normal class array. + Assert.Null (uintarrayarray_method.GetParameters () [0].KotlinType); + Assert.Null (uintarrayarray_method.KotlinReturnType); + } + + [Test] + public void UnsignedFields () + { + var klass = LoadClassFile ("UnsignedTypesKt.class"); + + var uint_field = klass.Fields.Single (m => m.Name == "UINT_CONST"); + var ushort_field = klass.Fields.Single (m => m.Name == "USHORT_CONST"); + var ulong_field = klass.Fields.Single (m => m.Name == "ULONG_CONST"); + var ubyte_field = klass.Fields.Single (m => m.Name == "UBYTE_CONST"); + + KotlinFixups.Fixup (new [] { klass }); + + Assert.AreEqual ("uint", uint_field.KotlinType); + Assert.AreEqual ("ushort", ushort_field.KotlinType); + Assert.AreEqual ("ulong", ulong_field.KotlinType); + Assert.AreEqual ("ubyte", ubyte_field.KotlinType); + } + + [Test] + public void UnsignedFieldsXml () + { + // Ensure Kotlin unsigned types end up in the xml + var klass = LoadClassFile ("UnsignedTypesKt.class"); + + KotlinFixups.Fixup (new [] { klass }); + + var xml = new XmlClassDeclarationBuilder (klass).ToXElement (); + + Assert.AreEqual ("uint", xml.Elements ("field").Single (f => f.Attribute ("name").Value == "UINT_CONST").Attribute ("type").Value); + Assert.AreEqual ("ushort", xml.Elements ("field").Single (f => f.Attribute ("name").Value == "USHORT_CONST").Attribute ("type").Value); + Assert.AreEqual ("ulong", xml.Elements ("field").Single (f => f.Attribute ("name").Value == "ULONG_CONST").Attribute ("type").Value); + Assert.AreEqual ("ubyte", xml.Elements ("field").Single (f => f.Attribute ("name").Value == "UBYTE_CONST").Attribute ("type").Value); + } + + [Test] + public void UnsignedMethodsXml () + { + // Ensure Kotlin unsigned types end up in the xml + var klass = LoadClassFile ("UnsignedTypes.class"); + + KotlinFixups.Fixup (new [] { klass }); + + var xml = new XmlClassDeclarationBuilder (klass).ToXElement (); + + Assert.AreEqual ("uint", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_uint-WZ4Q5Ns").Attribute ("return").Value); + Assert.AreEqual ("uint", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_uint-WZ4Q5Ns").Element ("parameter").Attribute ("type").Value); + + Assert.AreEqual ("ushort", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ushort-xj2QHRw").Attribute ("return").Value); + Assert.AreEqual ("ushort", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ushort-xj2QHRw").Element ("parameter").Attribute ("type").Value); + + Assert.AreEqual ("ulong", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ulong-VKZWuLQ").Attribute ("return").Value); + Assert.AreEqual ("ulong", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ulong-VKZWuLQ").Element ("parameter").Attribute ("type").Value); + + Assert.AreEqual ("ubyte", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ubyte-7apg3OU").Attribute ("return").Value); + Assert.AreEqual ("ubyte", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ubyte-7apg3OU").Element ("parameter").Attribute ("type").Value); + + Assert.AreEqual ("uint[]", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_uintarray--ajY-9A").Attribute ("return").Value); + Assert.AreEqual ("uint[]", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_uintarray--ajY-9A").Element ("parameter").Attribute ("type").Value); + + Assert.AreEqual ("ushort[]", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ushortarray-rL5Bavg").Attribute ("return").Value); + Assert.AreEqual ("ushort[]", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ushortarray-rL5Bavg").Element ("parameter").Attribute ("type").Value); + + Assert.AreEqual ("ulong[]", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ulongarray-QwZRm1k").Attribute ("return").Value); + Assert.AreEqual ("ulong[]", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ulongarray-QwZRm1k").Element ("parameter").Attribute ("type").Value); + + Assert.AreEqual ("ubyte[]", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ubytearray-GBYM_sE").Attribute ("return").Value); + Assert.AreEqual ("ubyte[]", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ubytearray-GBYM_sE").Element ("parameter").Attribute ("type").Value); + } } } diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/UnsignedTypes.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/UnsignedTypes.class new file mode 100644 index 0000000000000000000000000000000000000000..b55f4c2bd046c1d61f034fe468c6a3e38bb4d240 GIT binary patch literal 2972 zcma)7TT|Oc6#iDeAQZ4=b2BCh36Nkz9Rs;FB~6RdTnIvya ze@dU*nf9U6Odm2#`qtO}pH9!pl7(puX3*}Q^L_iB)!C!tzyJO76MzM5F!XPl+^CxR z?#73=I`08vQ1`S$EnCyfYIb#JPp{ZL2rx_(_v?1eFtd-|ztt_{KsRlz*0y<(L4IAY zziJq!%@9oG(|MZgHwI^j7Ee<{hgz+nGcY2l-nPt(?OSWNS7?LcxcHm8a<>|+bfM)ZBd58aM?}W-r+%(^mNPAYT3MLTeQolP}VOoj8xv}mHp?Mr5)(R$a7bL zVLDYjeItt}JS8DhT}*HFVH{T!T*g%fNt|!Q&sFmktm5sQc1TI0S16^+luk(u!>v3u zX>0Gc*AM3R9arc6p=XwpQEnzSdmm&>pCBZrntmnn;q7@9ZarOlx-q*7(Cem3dF zmqbPv(_ip041CcWzHgX@{eU5m5^p+(c?Cf%(9LFk3kk;#yLR}mkL>t}s6n|$l?m)6lH!7j4_;9F zNU5K|hzmxDEbORjM}4xY#t4iPIN*W^&_^e0SsqbR6BKZi zG?K1Hk~D=q=GtQ(H7H+HFA;c|z;PEG_rWXbRZ^NDaMA@Qi7f0X*Pimp*VO9-P7|1N z!ITfC)tS#fNv83x-q-d44(qu$sTeez*_m-p#0rgJ^3jKjRT_tzC}i}K zB^U}W5FSHc7aHn08vS&d^Ue|Ls1bF}p?0AWKSv{mc6#L=V%O3zR%0YP)Cgs=NySHG=|U77)6q@AnM~j-MFX^c{YCWBPM2lZOcj+BPW#P zR+Z4NE+*+T&xtLnX^iGIdU~k)J4OssxYlKeM8^=<&kiw-RF@$}JBCP~9byKvU4}?@ z4Dr?3Au_npWr(qkA+l$MVB*)D2;#a0QGEQNnLlvzH(H$R2oXoWuSsz4I)HVDRYZyA z-wC^g5N?+d$m5Rle-~eS*1bHw!6JEazYNClEgqCH%hAj69mhP!kh4%YzUN4A409}T z9VpMCazr=^ P9M3qWIMtz7rXk=zCn5vF literal 0 HcmV?d00001 diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/UnsignedTypes.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/UnsignedTypes.kt new file mode 100644 index 000000000..269d9c43a --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/UnsignedTypes.kt @@ -0,0 +1,28 @@ +@kotlin.ExperimentalUnsignedTypes +const val UINT_CONST: UInt = 3u + +@kotlin.ExperimentalUnsignedTypes +const val USHORT_CONST: UShort = 3u + +@kotlin.ExperimentalUnsignedTypes +const val ULONG_CONST: ULong = 3u + +@kotlin.ExperimentalUnsignedTypes +const val UBYTE_CONST: UByte = 3u + +@kotlin.ExperimentalUnsignedTypes +public class UnsignedTypes { + public fun foo_uint (value : Int) : Int { return value; } + public fun foo_uint (value : UInt) : UInt { return value; } + public fun foo_ushort (value : Short) : Short { return value; } + public fun foo_ushort (value : UShort) : UShort { return value; } + public fun foo_ulong (value : Long) : Long { return value; } + public fun foo_ulong (value : ULong) : ULong { return value; } + public fun foo_ubyte (value : Byte) : Byte { return value; } + public fun foo_ubyte (value : UByte) : UByte { return value; } + public fun foo_uintarray (value : UIntArray) : UIntArray { return value; } + public fun foo_ushortarray (value : UShortArray) : UShortArray { return value; } + public fun foo_ulongarray (value : ULongArray) : ULongArray { return value; } + public fun foo_ubytearray (value : UByteArray) : UByteArray { return value; } + public fun foo_uintarrayarray (value : Array) : Array { return value; } +} \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/UnsignedTypesKt.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/UnsignedTypesKt.class new file mode 100644 index 0000000000000000000000000000000000000000..4140b0fe40bf1eae2de124a4a035fe13510d797c GIT binary patch literal 1101 zcmb7?%Wl&^6o&t?otty9dv98>0y}mMj@Js3U4>srzzlKdyvyFKps@XabAp*j)K$L*U2#672 z&H@Pn;v*nQKx!7S39xK^_*o-^(R5&ahaq*o>+D5~N`NfB1 z7X5DLh`;n&G%tOIR%SqKLxVwgVIYkV!gM$Hf}25v@;DMxc?#Azw`qIzL~H*u?qIf(^{+Y(C>F^NTqkVHlzDiM}QN!*c`m#`$_5*rdp GiNrsSy3{xT literal 0 HcmV?d00001 diff --git a/src/Xamarin.Android.Tools.Bytecode/XmlClassDeclarationBuilder.cs b/src/Xamarin.Android.Tools.Bytecode/XmlClassDeclarationBuilder.cs index 4d41072a2..3a42b5e2a 100644 --- a/src/Xamarin.Android.Tools.Bytecode/XmlClassDeclarationBuilder.cs +++ b/src/Xamarin.Android.Tools.Bytecode/XmlClassDeclarationBuilder.cs @@ -332,6 +332,8 @@ XElement GetMethod (string element, string name, MethodInfo method, string retur var ret = returns != null ? new XAttribute ("return", SignatureToGenericJavaTypeName (returns)) : null; + if (!string.IsNullOrWhiteSpace (method.KotlinReturnType)) + ret?.SetValue (method.KotlinReturnType); var jniRet = returns != null ? new XAttribute ("jni-return", returns) : null; @@ -398,6 +400,8 @@ IEnumerable GetMethodParameters (MethodInfo method) genericType = genericType.Substring (1); } genericType = SignatureToGenericJavaTypeName (genericType); + if (!string.IsNullOrWhiteSpace (p.KotlinType)) + genericType = p.KotlinType; if (varargArray) { type += "..."; genericType += "..."; @@ -427,6 +431,9 @@ IEnumerable GetFields () var visibility = GetVisibility (field.AccessFlags); if (visibility == "private" || visibility == "") continue; + var type = new XAttribute ("type", SignatureToJavaTypeName (field.Descriptor)); + if (!string.IsNullOrWhiteSpace (field.KotlinType)) + type.SetValue (field.KotlinType); yield return new XElement ("field", new XAttribute ("deprecated", GetDeprecatedValue (field.Attributes)), new XAttribute ("final", (field.AccessFlags & FieldAccessFlags.Final) != 0), @@ -434,7 +441,7 @@ IEnumerable GetFields () new XAttribute ("static", (field.AccessFlags & FieldAccessFlags.Static) != 0), new XAttribute ("synthetic", (field.AccessFlags & FieldAccessFlags.Synthetic) != 0), new XAttribute ("transient", (field.AccessFlags & FieldAccessFlags.Transient) != 0), - new XAttribute ("type", SignatureToJavaTypeName (field.Descriptor)), + type, new XAttribute ("type-generic-aware", GetGenericType (field)), new XAttribute ("jni-signature", field.Descriptor), GetValue (field), diff --git a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs index 1f355c722..888081edf 100644 --- a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs +++ b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/CodeGenerator.cs @@ -1207,7 +1207,7 @@ public void WriteMethodInvokerBody (Method method, string indent) writer.WriteLine ("{0}{1}", indent, prep); WriteParameterListCallArgs (method.Parameters, indent, invoker: true); string env_method = "Call" + method.RetVal.CallMethodPrefix + "Method"; - string call = "JNIEnv." + env_method + " (" + + string call = method.RetVal.ReturnCast + "JNIEnv." + env_method + " (" + Context.ContextType.GetObjectHandleProperty ("this") + ", " + method.EscapedIdName + method.Parameters.GetCallArgs (opt, invoker: true) + ")"; if (method.IsVoid) writer.WriteLine ("{0}{1};", indent, call); diff --git a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/JavaInteropCodeGenerator.cs b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/JavaInteropCodeGenerator.cs index 5032a5085..7554007d7 100644 --- a/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/JavaInteropCodeGenerator.cs +++ b/tools/generator/Java.Interop.Tools.Generator.CodeGeneration/JavaInteropCodeGenerator.cs @@ -19,6 +19,10 @@ static string GetInvokeType (string type) case "Short": return "Int16"; case "Long": return "Int64"; case "Float": return "Single"; + case "UInt": return "Int32"; + case "UShort": return "Int16"; + case "ULong": return "Int64"; + case "UByte": return "SByte"; default: return type; } } @@ -171,7 +175,7 @@ internal override void WriteMethodBody (Method method, string indent, GenBase ty if (!method.IsVoid) { var r = invokeType == "Object" ? "__rm.Handle" : "__rm"; - writer.WriteLine ("{0}return {1};", indent, method.RetVal.FromNative (opt, r, true)); + writer.WriteLine ("{0}return {2}{1};", indent, method.RetVal.FromNative (opt, r, true), method.RetVal.ReturnCast); } indent = oldindent; @@ -196,19 +200,21 @@ internal override void WriteFieldGetBody (Field field, string indent, GenBase ty var invoke = "Get{0}Value"; invoke = string.Format (invoke, invokeType); - writer.WriteLine ("{0}var __v = _members.{1}.{2} (__id{3});", + writer.WriteLine ("{0}var __v = {4}_members.{1}.{2} (__id{3});", indent, indirect, invoke, - field.IsStatic ? "" : ", this"); + field.IsStatic ? "" : ", this", + field.Symbol.ReturnCast); if (field.Symbol.IsArray) { writer.WriteLine ("{0}return global::Android.Runtime.JavaArray<{1}>.FromJniHandle (__v.Handle, JniHandleOwnership.TransferLocalRef);", indent, opt.GetOutputName (field.Symbol.ElementType)); } else if (field.Symbol.NativeType != field.Symbol.FullName) { - writer.WriteLine ("{0}return {1};", + writer.WriteLine ("{0}return {2}{1};", indent, - field.Symbol.FromNative (opt, invokeType != "Object" ? "__v" : "__v.Handle", true)); + field.Symbol.FromNative (opt, invokeType != "Object" ? "__v" : "__v.Handle", true), + field.Symbol.ReturnCast); } else { writer.WriteLine ("{0}return __v;", indent); } diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs index b74ba3d8a..3f5b51fac 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Field.cs @@ -23,7 +23,7 @@ public class Field : ApiVersionsSupport.IApiAvailability public string Value { get; set; } public string Visibility { get; set; } - internal string GetMethodPrefix => (Symbol is SimpleSymbol || Symbol.IsEnum) ? StringRocks.MemberToPascalCase (Symbol.JavaName) : "Object"; + internal string GetMethodPrefix => TypeNameUtilities.GetCallPrefix (Symbol); internal string ID => JavaName + "_jfieldId"; diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs index 3e871f0f3..0de9cf0a0 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/GenBase.cs @@ -33,6 +33,8 @@ protected GenBase (GenBaseSupport support) public string DefaultValue { get; set; } public bool HasVirtualMethods { get; set; } + public string ReturnCast => string.Empty; + // This means Ctors/Methods/Properties/Fields has not been populated yet. // If this type is retrieved from the SymbolTable, it will call PopulateAction // to fill in members before returning it to the user. diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Method.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Method.cs index 67e857b92..60c84c6aa 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Method.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Method.cs @@ -108,7 +108,7 @@ internal string CalculateEventName (Func checkNameDuplicate) public string EscapedCallbackName => IdentifierValidator.CreateValidIdentifier ($"cb_{JavaName}{IDSignature}", true); - public string EscapedIdName => "id_" + JavaName.Replace ("<", "_x60_").Replace (">", "_x62_") + IDSignature; + public string EscapedIdName => IdentifierValidator.CreateValidIdentifier ($"id_{JavaName}{IDSignature}", true); internal void FillReturnType () { diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs index 4a738d501..be0869f3a 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs @@ -87,10 +87,7 @@ public bool IsKotlinNameMangled { if (method.JavaName.IndexOf ("-impl") >= 0) return true; - var index = method.JavaName.IndexOf ('-'); - - // `add-V5j3Lk8` is always a 7 character hashcode - return index >= 0 && method.JavaName.Length - index == 8; + return method.JavaName.Length >= 8 && method.JavaName [method.JavaName.Length - 8] == '-'; } return false; diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/ReturnValue.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/ReturnValue.cs index 8ee014132..bcdacec12 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/ReturnValue.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/ReturnValue.cs @@ -22,14 +22,7 @@ public ReturnValue (Method owner, string java_type, string managed_type, bool is this.is_enumified = isEnumified; } - public string CallMethodPrefix { - get { - if (sym is SimpleSymbol || sym.IsEnum) - return StringRocks.MemberToPascalCase (sym.JavaName); - else - return "Object"; - } - } + public string CallMethodPrefix => TypeNameUtilities.GetCallPrefix (sym); public string DefaultValue { get { return sym.DefaultValue; } @@ -89,6 +82,8 @@ public string RawJavaType { get { return raw_type; } } + public string ReturnCast => sym?.ReturnCast ?? string.Empty; + public string FromNative (CodeGenerationOptions opt, string var_name, bool owned) { if (!string.IsNullOrEmpty (managed_type) && (sym is ClassGen || sym is InterfaceGen)) { diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/ArraySymbol.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/ArraySymbol.cs index cb59c5b16..deb5f82ad 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/ArraySymbol.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/ArraySymbol.cs @@ -64,6 +64,8 @@ public bool IsArray { get { return true; } } + public string ReturnCast => string.Empty; + public string GetObjectHandleProperty (string variable) { return sym.GetObjectHandleProperty (variable); @@ -120,10 +122,25 @@ public string[] PreCallback (CodeGenerationOptions opt, string var_name, bool ow public string[] PreCall (CodeGenerationOptions opt, string var_name) { - return new string[] { String.Format ("IntPtr {0} = JNIEnv.NewArray ({1});", opt.GetSafeIdentifier (TypeNameUtilities.GetNativeName (var_name)), opt.GetSafeIdentifier (var_name)) }; + + return new string[] { String.Format ("IntPtr {0} = JNIEnv.NewArray ({2}{1});", opt.GetSafeIdentifier (TypeNameUtilities.GetNativeName (var_name)), opt.GetSafeIdentifier (var_name), GetPreCallCast ()) }; } public bool NeedsPrep { get { return true; } } + + string GetPreCallCast () + { + switch (sym.FullName) { + case "uint": + return "(int[])(object)"; + case "ushort": + return "(short[])(object)"; + case "ulong": + return "(long[])(object)"; + default: + return string.Empty; + } + } } } diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/CharSequenceSymbol.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/CharSequenceSymbol.cs index 8a026cd4d..329e66eb3 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/CharSequenceSymbol.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/CharSequenceSymbol.cs @@ -41,6 +41,8 @@ public string ElementType { get { return null; } } + public string ReturnCast => string.Empty; + public string GetObjectHandleProperty (string variable) { return $"((global::Java.Lang.Object) {variable}).Handle"; diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/CollectionSymbol.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/CollectionSymbol.cs index 42ef65a5f..e97dabc92 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/CollectionSymbol.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/CollectionSymbol.cs @@ -56,6 +56,8 @@ public bool MayHaveManagedGenericArguments { get { return true; } } + public string ReturnCast => string.Empty; + public string GetObjectHandleProperty (string variable) { return $"((global::Java.Lang.Object) {variable}).Handle"; diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/EnumSymbol.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/EnumSymbol.cs index a404cbdd1..8782b556b 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/EnumSymbol.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/EnumSymbol.cs @@ -46,6 +46,8 @@ public string ElementType { get { return null; } } + public string ReturnCast => string.Empty; + public string GetObjectHandleProperty (string variable) { return null; diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/FormatSymbol.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/FormatSymbol.cs index 465d6be82..859a5f162 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/FormatSymbol.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/FormatSymbol.cs @@ -13,8 +13,9 @@ public class FormatSymbol : ISymbol { string native_type; string to_fmt; string type; + string return_cast; - public FormatSymbol (string default_value, string java_type, string jni_type, string native_type, string type, string from_fmt, string to_fmt) + public FormatSymbol (string default_value, string java_type, string jni_type, string native_type, string type, string from_fmt, string to_fmt, string returnCast) { this.default_value = default_value; this.java_type = java_type; @@ -23,6 +24,7 @@ public FormatSymbol (string default_value, string java_type, string jni_type, st this.type = type; this.from_fmt = from_fmt; this.to_fmt = to_fmt; + this.return_cast = returnCast ?? string.Empty; } public string DefaultValue { @@ -61,6 +63,8 @@ public string ElementType { get { return null; } } + public string ReturnCast => return_cast; + public string GetObjectHandleProperty (string variable) { return null; diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/GeneratedEnumSymbol.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/GeneratedEnumSymbol.cs index 09f61bd61..55638ddd0 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/GeneratedEnumSymbol.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/GeneratedEnumSymbol.cs @@ -77,6 +77,8 @@ public string ElementType { get { return enum_type; } } + public string ReturnCast => string.Empty; + public string GetObjectHandleProperty (string variable) { return null; diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/GenericSymbol.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/GenericSymbol.cs index 0865d333f..2c9fc2a47 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/GenericSymbol.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/GenericSymbol.cs @@ -63,6 +63,8 @@ public ISymbol [] TypeParams { get { return type_params; } } + public string ReturnCast => string.Empty; + public string GetObjectHandleProperty (string variable) { return gen.GetObjectHandleProperty (variable); diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/GenericTypeParameter.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/GenericTypeParameter.cs index b1642f290..d1f901910 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/GenericTypeParameter.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/GenericTypeParameter.cs @@ -63,6 +63,8 @@ public string ElementType { get { return null; } } + public string ReturnCast => string.Empty; + public string GetObjectHandleProperty (string variable) { return null; diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/ISymbol.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/ISymbol.cs index c8831d722..c5182c83a 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/ISymbol.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/ISymbol.cs @@ -13,6 +13,7 @@ public interface ISymbol { bool IsEnum { get; } bool IsArray { get; } string ElementType { get; } + string ReturnCast { get; } string GetObjectHandleProperty (string variable); diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/SimpleSymbol.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/SimpleSymbol.cs index e5e04eca6..c8d3e9e2b 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/SimpleSymbol.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/SimpleSymbol.cs @@ -4,8 +4,8 @@ namespace MonoDroid.Generation { public class SimpleSymbol : FormatSymbol { - public SimpleSymbol (string default_value, string java_type, string type, string jni_type, string native_type = null, string from_fmt="{0}", string to_fmt="{0}") - : base (default_value, java_type, jni_type, native_type ?? type, type, from_fmt, to_fmt) + public SimpleSymbol (string default_value, string java_type, string type, string jni_type, string native_type = null, string from_fmt="{0}", string to_fmt="{0}", string returnCast = null) + : base (default_value, java_type, jni_type, native_type ?? type, type, from_fmt, to_fmt, returnCast) { } diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/StreamSymbol.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/StreamSymbol.cs index 78068e45e..bf660341d 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/StreamSymbol.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/StreamSymbol.cs @@ -54,6 +54,8 @@ public string ElementType { get { return null; } } + public string ReturnCast => string.Empty; + public string GetObjectHandleProperty (string variable) { return $"((global::Java.Lang.Object) {variable}).Handle"; diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/StringSymbol.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/StringSymbol.cs index fe50266c2..d78799149 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/StringSymbol.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/StringSymbol.cs @@ -44,6 +44,8 @@ public string ElementType { get { return null; } } + public string ReturnCast => string.Empty; + public string GetObjectHandleProperty (string variable) { return $"((global::Java.Lang.Object) {variable}).Handle"; diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/SymbolTable.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/SymbolTable.cs index 0ffbd8317..5ae736a6b 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/SymbolTable.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/SymbolTable.cs @@ -33,6 +33,10 @@ public class SymbolTable { "int", "long", "short", + "ubyte", + "uint", + "ulong", + "ushort", "void", }; @@ -55,6 +59,10 @@ public SymbolTable () AddType (new SimpleSymbol ("0", "int", "int", "I")); AddType (new SimpleSymbol ("0L", "long", "long", "J")); AddType (new SimpleSymbol ("0", "short", "short", "S")); + AddType (new SimpleSymbol ("0", "uint", "uint", "I", returnCast: "(uint)")); + AddType (new SimpleSymbol ("0", "ushort", "ushort", "S", returnCast: "(ushort)")); + AddType (new SimpleSymbol ("0", "ulong", "ulong", "J", returnCast: "(ulong)")); + AddType (new SimpleSymbol ("0", "ubyte", "byte", "B", returnCast: "(byte)")); AddType ("Android.Graphics.Color", new ColorSymbol ()); char_seq = new CharSequenceSymbol (); instream_sym = new StreamSymbol ("InputStream"); diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/XmlPullParserSymbol.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/XmlPullParserSymbol.cs index c497d31b3..aff529793 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/XmlPullParserSymbol.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/XmlPullParserSymbol.cs @@ -41,6 +41,8 @@ public string ElementType { get { return null; } } + public string ReturnCast => string.Empty; + public string GetObjectHandleProperty (string variable) { return $"((global::Java.Lang.Object) {variable}).Handle"; diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/XmlResourceParserSymbol.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/XmlResourceParserSymbol.cs index 08a1b85b6..fa8df65f4 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/XmlResourceParserSymbol.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/Symbols/XmlResourceParserSymbol.cs @@ -41,6 +41,8 @@ public string ElementType { get { return null; } } + public string ReturnCast => string.Empty; + public string GetObjectHandleProperty (string variable) { return $"((global::Java.Lang.Object) {variable}).Handle"; diff --git a/tools/generator/Java.Interop.Tools.Generator.Transformation/KotlinFixups.cs b/tools/generator/Java.Interop.Tools.Generator.Transformation/KotlinFixups.cs index fc901fc5c..6545a0818 100644 --- a/tools/generator/Java.Interop.Tools.Generator.Transformation/KotlinFixups.cs +++ b/tools/generator/Java.Interop.Tools.Generator.Transformation/KotlinFixups.cs @@ -12,6 +12,8 @@ public static void Fixup (List gens) { foreach (var c in gens.OfType ()) FixupClass (c); + foreach (var i in gens.OfType ()) + FixupInterface (i); } private static void FixupClass (ClassGen c) @@ -20,9 +22,8 @@ private static void FixupClass (ClassGen c) // inaccessible from Java, like `add-impl` and `add-V5j3Lk8`. // We need to generate C# compatible names as well as prevent overriding // them as we cannot generate JCW for them. - var invalid_methods = c.Methods.Where (m => m.IsKotlinNameMangled).ToList (); - foreach (var method in invalid_methods) { + foreach (var method in c.Methods.Where (m => m.IsKotlinNameMangled)) { // If the method is virtual, mark it as !virtual as it can't be overridden in Java if (!method.IsFinal) @@ -31,15 +32,29 @@ private static void FixupClass (ClassGen c) if (method.IsVirtual) method.IsVirtual = false; - // Only run this if it's the default name (ie: not a user's "managedName") - if (method.Name == StringRocks.MemberToPascalCase (method.JavaName).Replace ('-', '_')) { - // We want to remove the hyphen and anything afterwards to fix mangled names, - // but a previous step converted it to an underscore. Remove the final - // underscore and anything after it. - var index = method.Name.LastIndexOf ('_'); + FixMethodName (method); + } + } + + private static void FixupInterface (InterfaceGen gen) + { + foreach (var method in gen.Methods.Where (m => m.IsKotlinNameMangled)) + FixMethodName (method); + } + + private static void FixMethodName (Method method) + { + // Only run this if it's the default name (ie: not a user's "managedName") + if (method.Name == StringRocks.MemberToPascalCase (method.JavaName).Replace ('-', '_')) { + // We want to remove the hyphen and anything afterwards to fix mangled names, + // but a previous step converted it to an underscore. Remove the final + // underscore and anything after it. + var index = method.Name.IndexOf ("_impl"); + if (index >= 0) method.Name = method.Name.Substring (0, index); - } + else + method.Name = method.Name.Substring (0, method.Name.Length - 8); } } } diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedArrayTypeMethodsClass.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedArrayTypeMethodsClass.txt new file mode 100644 index 000000000..5339561af --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedArrayTypeMethodsClass.txt @@ -0,0 +1,90 @@ +// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']" +[global::Android.Runtime.Register ("java/code/MyClass", DoNotGenerateAcw=true)] +public partial class MyClass { + + static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/MyClass", typeof (MyClass)); + internal static new IntPtr class_ref { + get { + return _members.JniPeerType.PeerReference.Handle; + } + } + + protected MyClass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer) {} + + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='Echo' and count(parameter)=1 and parameter[1][@type='uint[]']]" + [Register ("Echo", "([I)[I", "")] + public unsafe uint[] Echo (uint[] value) + { + const string __id = "Echo.([I)[I"; + IntPtr native_value = JNIEnv.NewArray ((int[])(object)value); + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (native_value); + var __rm = _members.InstanceMethods.InvokeAbstractObjectMethod (__id, this, __args); + return (uint[]) JNIEnv.GetArray (__rm.Handle, JniHandleOwnership.TransferLocalRef, typeof (uint)); + } finally { + if (value != null) { + JNIEnv.CopyArray (native_value, value); + JNIEnv.DeleteLocalRef (native_value); + } + } + } + + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='Echo' and count(parameter)=1 and parameter[1][@type='ushort[]']]" + [Register ("Echo", "([S)[S", "")] + public unsafe ushort[] Echo (ushort[] value) + { + const string __id = "Echo.([S)[S"; + IntPtr native_value = JNIEnv.NewArray ((short[])(object)value); + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (native_value); + var __rm = _members.InstanceMethods.InvokeAbstractObjectMethod (__id, this, __args); + return (ushort[]) JNIEnv.GetArray (__rm.Handle, JniHandleOwnership.TransferLocalRef, typeof (ushort)); + } finally { + if (value != null) { + JNIEnv.CopyArray (native_value, value); + JNIEnv.DeleteLocalRef (native_value); + } + } + } + + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='Echo' and count(parameter)=1 and parameter[1][@type='ulong[]']]" + [Register ("Echo", "([J)[J", "")] + public unsafe ulong[] Echo (ulong[] value) + { + const string __id = "Echo.([J)[J"; + IntPtr native_value = JNIEnv.NewArray ((long[])(object)value); + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (native_value); + var __rm = _members.InstanceMethods.InvokeAbstractObjectMethod (__id, this, __args); + return (ulong[]) JNIEnv.GetArray (__rm.Handle, JniHandleOwnership.TransferLocalRef, typeof (ulong)); + } finally { + if (value != null) { + JNIEnv.CopyArray (native_value, value); + JNIEnv.DeleteLocalRef (native_value); + } + } + } + + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='Echo' and count(parameter)=1 and parameter[1][@type='ubyte[]']]" + [Register ("Echo", "([B)[B", "")] + public unsafe byte[] Echo (byte[] value) + { + const string __id = "Echo.([B)[B"; + IntPtr native_value = JNIEnv.NewArray (value); + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (native_value); + var __rm = _members.InstanceMethods.InvokeAbstractObjectMethod (__id, this, __args); + return (byte[]) JNIEnv.GetArray (__rm.Handle, JniHandleOwnership.TransferLocalRef, typeof (byte)); + } finally { + if (value != null) { + JNIEnv.CopyArray (native_value, value); + JNIEnv.DeleteLocalRef (native_value); + } + } + } + +} diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedArrayTypePropertiesClass.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedArrayTypePropertiesClass.txt new file mode 100644 index 000000000..a139a6768 --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedArrayTypePropertiesClass.txt @@ -0,0 +1,130 @@ +// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']" +[global::Android.Runtime.Register ("java/code/MyClass", DoNotGenerateAcw=true)] +public partial class MyClass { + + static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/MyClass", typeof (MyClass)); + internal static new IntPtr class_ref { + get { + return _members.JniPeerType.PeerReference.Handle; + } + } + + protected MyClass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer) {} + + public unsafe uint[] UIntProp { + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='get_UIntProp' and count(parameter)=0]" + [Register ("get_UIntProp", "()[I", "")] + get { + const string __id = "get_UIntProp.()[I"; + try { + var __rm = _members.InstanceMethods.InvokeAbstractObjectMethod (__id, this, null); + return (uint[]) JNIEnv.GetArray (__rm.Handle, JniHandleOwnership.TransferLocalRef, typeof (uint)); + } finally { + } + } + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='set_UIntProp' and count(parameter)=1 and parameter[1][@type='uint[]']]" + [Register ("set_UIntProp", "([I)V", "")] + set { + const string __id = "set_UIntProp.([I)V"; + IntPtr native_value = JNIEnv.NewArray ((int[])(object)value); + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (native_value); + _members.InstanceMethods.InvokeAbstractVoidMethod (__id, this, __args); + } finally { + if (value != null) { + JNIEnv.CopyArray (native_value, value); + JNIEnv.DeleteLocalRef (native_value); + } + } + } + } + + public unsafe ushort[] UShortProp { + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='get_UShortProp' and count(parameter)=0]" + [Register ("get_UShortProp", "()[S", "")] + get { + const string __id = "get_UShortProp.()[S"; + try { + var __rm = _members.InstanceMethods.InvokeAbstractObjectMethod (__id, this, null); + return (ushort[]) JNIEnv.GetArray (__rm.Handle, JniHandleOwnership.TransferLocalRef, typeof (ushort)); + } finally { + } + } + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='set_UShortProp' and count(parameter)=1 and parameter[1][@type='ushort[]']]" + [Register ("set_UShortProp", "([S)V", "")] + set { + const string __id = "set_UShortProp.([S)V"; + IntPtr native_value = JNIEnv.NewArray ((short[])(object)value); + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (native_value); + _members.InstanceMethods.InvokeAbstractVoidMethod (__id, this, __args); + } finally { + if (value != null) { + JNIEnv.CopyArray (native_value, value); + JNIEnv.DeleteLocalRef (native_value); + } + } + } + } + + public unsafe ulong[] ULongProp { + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='get_ULongProp' and count(parameter)=0]" + [Register ("get_ULongProp", "()[J", "")] + get { + const string __id = "get_ULongProp.()[J"; + try { + var __rm = _members.InstanceMethods.InvokeAbstractObjectMethod (__id, this, null); + return (ulong[]) JNIEnv.GetArray (__rm.Handle, JniHandleOwnership.TransferLocalRef, typeof (ulong)); + } finally { + } + } + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='set_ULongProp' and count(parameter)=1 and parameter[1][@type='ulong[]']]" + [Register ("set_ULongProp", "([J)V", "")] + set { + const string __id = "set_ULongProp.([J)V"; + IntPtr native_value = JNIEnv.NewArray ((long[])(object)value); + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (native_value); + _members.InstanceMethods.InvokeAbstractVoidMethod (__id, this, __args); + } finally { + if (value != null) { + JNIEnv.CopyArray (native_value, value); + JNIEnv.DeleteLocalRef (native_value); + } + } + } + } + + public unsafe byte[] UByteProp { + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='get_UByteProp' and count(parameter)=0]" + [Register ("get_UByteProp", "()[B", "")] + get { + const string __id = "get_UByteProp.()[B"; + try { + var __rm = _members.InstanceMethods.InvokeAbstractObjectMethod (__id, this, null); + return (byte[]) JNIEnv.GetArray (__rm.Handle, JniHandleOwnership.TransferLocalRef, typeof (byte)); + } finally { + } + } + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='set_UByteProp' and count(parameter)=1 and parameter[1][@type='ubyte[]']]" + [Register ("set_UByteProp", "([B)V", "")] + set { + const string __id = "set_UByteProp.([B)V"; + IntPtr native_value = JNIEnv.NewArray (value); + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (native_value); + _members.InstanceMethods.InvokeAbstractVoidMethod (__id, this, __args); + } finally { + if (value != null) { + JNIEnv.CopyArray (native_value, value); + JNIEnv.DeleteLocalRef (native_value); + } + } + } + } + +} diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedTypeMethodsClass.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedTypeMethodsClass.txt new file mode 100644 index 000000000..b03f98129 --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedTypeMethodsClass.txt @@ -0,0 +1,70 @@ +// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']" +[global::Android.Runtime.Register ("java/code/MyClass", DoNotGenerateAcw=true)] +public partial class MyClass { + + static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/MyClass", typeof (MyClass)); + internal static new IntPtr class_ref { + get { + return _members.JniPeerType.PeerReference.Handle; + } + } + + protected MyClass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer) {} + + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='Echo' and count(parameter)=1 and parameter[1][@type='uint']]" + [Register ("Echo", "(I)I", "")] + public unsafe uint Echo (uint value) + { + const string __id = "Echo.(I)I"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (value); + var __rm = _members.InstanceMethods.InvokeAbstractInt32Method (__id, this, __args); + return (uint)__rm; + } finally { + } + } + + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='Echo' and count(parameter)=1 and parameter[1][@type='ushort']]" + [Register ("Echo", "(S)S", "")] + public unsafe ushort Echo (ushort value) + { + const string __id = "Echo.(S)S"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (value); + var __rm = _members.InstanceMethods.InvokeAbstractInt16Method (__id, this, __args); + return (ushort)__rm; + } finally { + } + } + + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='Echo' and count(parameter)=1 and parameter[1][@type='ulong']]" + [Register ("Echo", "(J)J", "")] + public unsafe ulong Echo (ulong value) + { + const string __id = "Echo.(J)J"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (value); + var __rm = _members.InstanceMethods.InvokeAbstractInt64Method (__id, this, __args); + return (ulong)__rm; + } finally { + } + } + + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='Echo' and count(parameter)=1 and parameter[1][@type='ubyte']]" + [Register ("Echo", "(B)B", "")] + public unsafe byte Echo (byte value) + { + const string __id = "Echo.(B)B"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (value); + var __rm = _members.InstanceMethods.InvokeAbstractSByteMethod (__id, this, __args); + return (byte)__rm; + } finally { + } + } + +} diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedTypePropertiesClass.txt b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedTypePropertiesClass.txt new file mode 100644 index 000000000..113e4c242 --- /dev/null +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorExpectedResults/JavaInterop1/WriteKotlinUnsignedTypePropertiesClass.txt @@ -0,0 +1,110 @@ +// Metadata.xml XPath class reference: path="/api/package[@name='java.code']/class[@name='MyClass']" +[global::Android.Runtime.Register ("java/code/MyClass", DoNotGenerateAcw=true)] +public partial class MyClass { + + static readonly JniPeerMembers _members = new JniPeerMembers ("java/code/MyClass", typeof (MyClass)); + internal static new IntPtr class_ref { + get { + return _members.JniPeerType.PeerReference.Handle; + } + } + + protected MyClass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer) {} + + public unsafe uint UIntProp { + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='get_UIntProp' and count(parameter)=0]" + [Register ("get_UIntProp", "()I", "")] + get { + const string __id = "get_UIntProp.()I"; + try { + var __rm = _members.InstanceMethods.InvokeAbstractInt32Method (__id, this, null); + return (uint)__rm; + } finally { + } + } + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='set_UIntProp' and count(parameter)=1 and parameter[1][@type='uint']]" + [Register ("set_UIntProp", "(I)V", "")] + set { + const string __id = "set_UIntProp.(I)V"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (value); + _members.InstanceMethods.InvokeAbstractVoidMethod (__id, this, __args); + } finally { + } + } + } + + public unsafe ushort UShortProp { + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='get_UShortProp' and count(parameter)=0]" + [Register ("get_UShortProp", "()S", "")] + get { + const string __id = "get_UShortProp.()S"; + try { + var __rm = _members.InstanceMethods.InvokeAbstractInt16Method (__id, this, null); + return (ushort)__rm; + } finally { + } + } + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='set_UShortProp' and count(parameter)=1 and parameter[1][@type='ushort']]" + [Register ("set_UShortProp", "(S)V", "")] + set { + const string __id = "set_UShortProp.(S)V"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (value); + _members.InstanceMethods.InvokeAbstractVoidMethod (__id, this, __args); + } finally { + } + } + } + + public unsafe ulong ULongProp { + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='get_ULongProp' and count(parameter)=0]" + [Register ("get_ULongProp", "()J", "")] + get { + const string __id = "get_ULongProp.()J"; + try { + var __rm = _members.InstanceMethods.InvokeAbstractInt64Method (__id, this, null); + return (ulong)__rm; + } finally { + } + } + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='set_ULongProp' and count(parameter)=1 and parameter[1][@type='ulong']]" + [Register ("set_ULongProp", "(J)V", "")] + set { + const string __id = "set_ULongProp.(J)V"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (value); + _members.InstanceMethods.InvokeAbstractVoidMethod (__id, this, __args); + } finally { + } + } + } + + public unsafe byte UByteProp { + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='get_UByteProp' and count(parameter)=0]" + [Register ("get_UByteProp", "()B", "")] + get { + const string __id = "get_UByteProp.()B"; + try { + var __rm = _members.InstanceMethods.InvokeAbstractSByteMethod (__id, this, null); + return (byte)__rm; + } finally { + } + } + // Metadata.xml XPath method reference: path="/api/package[@name='java.code']/class[@name='MyClass']/method[@name='set_UByteProp' and count(parameter)=1 and parameter[1][@type='ubyte']]" + [Register ("set_UByteProp", "(B)V", "")] + set { + const string __id = "set_UByteProp.(B)V"; + try { + JniArgumentValue* __args = stackalloc JniArgumentValue [1]; + __args [0] = new JniArgumentValue (value); + _members.InstanceMethods.InvokeAbstractVoidMethod (__id, this, __args); + } finally { + } + } + } + +} diff --git a/tools/generator/Tests/Unit-Tests/CodeGeneratorTests.cs b/tools/generator/Tests/Unit-Tests/CodeGeneratorTests.cs index 25ec4f0f8..8df7ff753 100644 --- a/tools/generator/Tests/Unit-Tests/CodeGeneratorTests.cs +++ b/tools/generator/Tests/Unit-Tests/CodeGeneratorTests.cs @@ -11,6 +11,94 @@ namespace generatortests class JavaInteropCodeGeneratorTests : CodeGeneratorTests { protected override CodeGenerationTarget Target => CodeGenerationTarget.JavaInterop1; + + [Test] + public void WriteKotlinUnsignedTypeMethodsClass () + { + var @class = new TestClass ("Object", "java.code.MyClass"); + + @class.AddMethod (SupportTypeBuilder.CreateMethod (@class, "Echo", options, "uint", false, false, new Parameter ("value", "uint", "uint", false))); + @class.AddMethod (SupportTypeBuilder.CreateMethod (@class, "Echo", options, "ushort", false, false, new Parameter ("value", "ushort", "ushort", false))); + @class.AddMethod (SupportTypeBuilder.CreateMethod (@class, "Echo", options, "ulong", false, false, new Parameter ("value", "ulong", "ulong", false))); + @class.AddMethod (SupportTypeBuilder.CreateMethod (@class, "Echo", options, "ubyte", false, false, new Parameter ("value", "ubyte", "byte", false))); + + // Kotlin methods with unsigned types are name-mangled and don't support virtual + foreach (var m in @class.Methods) + m.IsVirtual = false; + + generator.Context.ContextTypes.Push (@class); + generator.WriteClass (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteKotlinUnsignedTypeMethodsClass)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteKotlinUnsignedTypePropertiesClass () + { + var @class = new TestClass ("Object", "java.code.MyClass"); + + @class.Properties.Add (SupportTypeBuilder.CreateProperty (@class, "UIntProp", "uint", options, false, false)); + @class.Properties.Add (SupportTypeBuilder.CreateProperty (@class, "UShortProp", "ushort", options, false, false)); + @class.Properties.Add (SupportTypeBuilder.CreateProperty (@class, "ULongProp", "ulong", options, false, false)); + @class.Properties.Add (SupportTypeBuilder.CreateProperty (@class, "UByteProp", "ubyte", options, false, false)); + + // Kotlin methods with unsigned types are name-mangled and don't support virtual + foreach (var m in @class.Properties) { + m.Getter.IsVirtual = false; + m.Setter.IsVirtual = false; + } + + generator.Context.ContextTypes.Push (@class); + generator.WriteClass (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteKotlinUnsignedTypePropertiesClass)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteKotlinUnsignedArrayTypeMethodsClass () + { + var @class = new TestClass ("Object", "java.code.MyClass"); + + @class.AddMethod (SupportTypeBuilder.CreateMethod (@class, "Echo", options, "uint[]", false, false, new Parameter ("value", "uint[]", "uint[]", false))); + @class.AddMethod (SupportTypeBuilder.CreateMethod (@class, "Echo", options, "ushort[]", false, false, new Parameter ("value", "ushort[]", "ushort[]", false))); + @class.AddMethod (SupportTypeBuilder.CreateMethod (@class, "Echo", options, "ulong[]", false, false, new Parameter ("value", "ulong[]", "ulong[]", false))); + @class.AddMethod (SupportTypeBuilder.CreateMethod (@class, "Echo", options, "ubyte[]", false, false, new Parameter ("value", "ubyte[]", "byte[]", false))); + + // Kotlin methods with unsigned types are name-mangled and don't support virtual + foreach (var m in @class.Methods) + m.IsVirtual = false; + + generator.Context.ContextTypes.Push (@class); + generator.WriteClass (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteKotlinUnsignedArrayTypeMethodsClass)), writer.ToString ().NormalizeLineEndings ()); + } + + [Test] + public void WriteKotlinUnsignedArrayTypePropertiesClass () + { + var @class = new TestClass ("Object", "java.code.MyClass"); + + @class.Properties.Add (SupportTypeBuilder.CreateProperty (@class, "UIntProp", "uint[]", options, false, false)); + @class.Properties.Add (SupportTypeBuilder.CreateProperty (@class, "UShortProp", "ushort[]", options, false, false)); + @class.Properties.Add (SupportTypeBuilder.CreateProperty (@class, "ULongProp", "ulong[]", options, false, false)); + @class.Properties.Add (SupportTypeBuilder.CreateProperty (@class, "UByteProp", "ubyte[]", options, false, false)); + + // Kotlin methods with unsigned types are name-mangled and don't support virtual + foreach (var m in @class.Properties) { + m.Getter.IsVirtual = false; + m.Setter.IsVirtual = false; + } + + generator.Context.ContextTypes.Push (@class); + generator.WriteClass (@class, string.Empty, new GenerationInfo ("", "", "MyAssembly")); + generator.Context.ContextTypes.Pop (); + + Assert.AreEqual (GetTargetedExpected (nameof (WriteKotlinUnsignedArrayTypePropertiesClass)), writer.ToString ().NormalizeLineEndings ()); + } } [TestFixture] diff --git a/tools/generator/Tests/Unit-Tests/KotlinFixupsTests.cs b/tools/generator/Tests/Unit-Tests/KotlinFixupsTests.cs index 1e4c90081..1c4ac1a89 100644 --- a/tools/generator/Tests/Unit-Tests/KotlinFixupsTests.cs +++ b/tools/generator/Tests/Unit-Tests/KotlinFixupsTests.cs @@ -26,7 +26,7 @@ public void CreateMethod_EnsureKotlinImplFix () [Test] public void CreateMethod_EnsureKotlinHashcodeFix () { - var xml = XDocument.Parse (""); + var xml = XDocument.Parse (""); var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class")); KotlinFixups.Fixup (new [] { (GenBase) klass }.ToList ()); diff --git a/tools/generator/Tests/generator-Tests.csproj b/tools/generator/Tests/generator-Tests.csproj index 58d073b8e..b8c7f849d 100644 --- a/tools/generator/Tests/generator-Tests.csproj +++ b/tools/generator/Tests/generator-Tests.csproj @@ -572,6 +572,18 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/tools/generator/Utilities/TypeNameUtilities.cs b/tools/generator/Utilities/TypeNameUtilities.cs index 371ab068e..56a620f0a 100644 --- a/tools/generator/Utilities/TypeNameUtilities.cs +++ b/tools/generator/Utilities/TypeNameUtilities.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using MonoDroid.Utils; namespace MonoDroid.Generation { @@ -101,5 +102,27 @@ public static string StudlyCase (string name) } return builder.ToString (); } + + public static string GetCallPrefix (ISymbol symbol) + { + if (symbol is SimpleSymbol || symbol.IsEnum) { + var ret = StringRocks.MemberToPascalCase (symbol.JavaName); + + // We do not have unsigned versions of GetIntValue, etc. + // We use the signed versions and cast the result + if (ret == "Uint") + return "Int"; + if (ret == "Ushort") + return "Short"; + if (ret == "Ulong") + return "Long"; + if (ret == "Ubyte") + return "Byte"; + + return ret; + + } else + return "Object"; + } } }