diff --git a/src/Xamarin.Android.Tools.Bytecode/ClassFile.cs b/src/Xamarin.Android.Tools.Bytecode/ClassFile.cs index 6968509e7..2ab54dd28 100644 --- a/src/Xamarin.Android.Tools.Bytecode/ClassFile.cs +++ b/src/Xamarin.Android.Tools.Bytecode/ClassFile.cs @@ -186,6 +186,8 @@ public bool IsStatic { public bool IsEnum { get {return (AccessFlags & ClassAccessFlags.Enum) != 0;} } + + public override string ToString () => ThisClass?.Name.Value; } [Flags] diff --git a/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs b/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs index 0aee8a5d5..62adb9a5d 100644 --- a/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs +++ b/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; @@ -8,7 +8,6 @@ using System.Xml.Linq; using System.Xml.XPath; using System.Text; -using Xamarin.Android.Tools.Bytecode.Kotlin; namespace Xamarin.Android.Tools.Bytecode { diff --git a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinClassMetadata.cs b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinClassMetadata.cs index 1f7944df3..901f5f8f5 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinClassMetadata.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinClassMetadata.cs @@ -1,29 +1,48 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using org.jetbrains.kotlin.metadata.jvm; using Type = org.jetbrains.kotlin.metadata.jvm.Type; namespace Xamarin.Android.Tools.Bytecode { - public class KotlinClass + public class KotlinFile + { + public List Functions { get; set; } + public List Properties { get; set; } + public List TypeAliases { get; set; } + public KotlinTypeTable TypeTable { get; set; } + public KotlinVersionRequirementTable VersionRequirementTable { get; set; } + + internal static KotlinFile FromProtobuf (Package c, JvmNameResolver resolver) + { + return new KotlinFile { + Functions = c.Functions?.Select (f => KotlinFunction.FromProtobuf (f, resolver)).ToList (), + Properties = c.Properties?.Select (p => KotlinProperty.FromProtobuf (p, resolver)).ToList (), + TypeAliases = c.TypeAlias?.Select (tp => KotlinTypeAlias.FromProtobuf (tp, resolver)).ToList (), + TypeTable = KotlinTypeTable.FromProtobuf (c.TypeTable, resolver), + VersionRequirementTable = KotlinVersionRequirementTable.FromProtobuf (c.VersionRequirementTable, resolver) + }; + } + } + + public class KotlinClass : KotlinFile { public string CompanionObjectName { get; set; } public List Constructors { get; set; } public List EnumEntries { get; set; } public KotlinClassFlags Flags { get; set; } public string FullyQualifiedName { get; set; } - public List Functions { get; set; } + public KotlinClassInheritability Inheritability { get; set; } public List NestedClassNames { get; set; } = new List (); - public List Properties { get; set; } + public KotlinClassType ObjectType { get; set; } public List SealedSubclassFullyQualifiedNames { get; set; } public List SuperTypeIds { get; set; } public List SuperTypes { get; set; } - public List TypeAliases { get; set; } public List TypeParameters { get; set; } - public KotlinTypeTable TypeTable { get; set; } public int [] VersionRequirements { get; set; } - public KotlinVersionRequirementTable VersionRequirementTable { get; set; } + public KotlinClassVisibility Visibility { get; set; } internal static KotlinClass FromProtobuf (Class c, JvmNameResolver resolver) { @@ -34,7 +53,9 @@ internal static KotlinClass FromProtobuf (Class c, JvmNameResolver resolver) Flags = (KotlinClassFlags)c.Flags, FullyQualifiedName = c.FqName > 0 ? resolver.GetString (c.FqName) : null, Functions = c.Functions?.Select (f => KotlinFunction.FromProtobuf (f, resolver)).ToList (), + Inheritability = (KotlinClassInheritability)((c.Flags & 0b110000) >> 4), NestedClassNames = c.NestedClassNames?.Select (n => resolver.GetString (n)).ToList (), + ObjectType = (KotlinClassType) ((c.Flags & 0b111000000) >> 6), Properties = c.Properties?.Select (p => KotlinProperty.FromProtobuf (p, resolver)).ToList (), SealedSubclassFullyQualifiedNames = c.SealedSubclassFqNames?.Select (n => resolver.GetString (n)).ToList (), SuperTypeIds = c.SupertypeIds?.Select (n => resolver.GetString (n)).ToList (), @@ -43,28 +64,40 @@ internal static KotlinClass FromProtobuf (Class c, JvmNameResolver resolver) TypeParameters = c.TypeParameters?.Select (tp => KotlinTypeParameter.FromProtobuf (tp, resolver)).ToList (), VersionRequirements = c.VersionRequirements, TypeTable = KotlinTypeTable.FromProtobuf (c.TypeTable, resolver), - VersionRequirementTable = KotlinVersionRequirementTable.FromProtobuf (c.VersionRequirementTable, resolver) + VersionRequirementTable = KotlinVersionRequirementTable.FromProtobuf (c.VersionRequirementTable, resolver), + Visibility = (KotlinClassVisibility)((c.Flags & 0b1110) >> 1) }; } } - public class KotlinConstructor + public class KotlinMethodBase { - public int Flags { get; set; } - public List ValueParameters { get; set; } public int [] VersionRequirements { get; set; } + public virtual string GetSignature () => string.Empty; + } + + public class KotlinConstructor : KotlinMethodBase + { + public KotlinConstructorFlags Flags { get; set; } + public List ValueParameters { get; set; } + internal static KotlinConstructor FromProtobuf (Constructor c, JvmNameResolver resolver) { if (c is null) return null; return new KotlinConstructor { - Flags = c.Flags, + Flags = (KotlinConstructorFlags)c.Flags, ValueParameters = c.ValueParameters?.Select (vp => KotlinValueParameter.FromProtobuf (vp, resolver)).ToList (), VersionRequirements = c.VersionRequirements }; } + + public override string GetSignature () + { + return $"({ValueParameters.GetSignature ()})V"; + } } public class KotlinAnnotation @@ -113,7 +146,7 @@ public class KotlinAnnotationArgumentValue public KotlinAnnotation Annotation { get; set; } public List ArrayElements { get; set; } public int ArrayDimensionCount { get; set; } - public int Flags { get; set; } + public KotlinAnnotationFlags Flags { get; set; } internal static KotlinAnnotationArgumentValue FromProtobuf (org.jetbrains.kotlin.metadata.jvm.Annotation.Argument.Value value, JvmNameResolver resolver) { @@ -131,7 +164,7 @@ internal static KotlinAnnotationArgumentValue FromProtobuf (org.jetbrains.kotlin Annotation = KotlinAnnotation.FromProtobuf (value.Annotation, resolver), ArrayDimensionCount = value.ArrayDimensionCount, ArrayElements = value.ArrayElements?.Select (vp => KotlinAnnotationArgumentValue.FromProtobuf (vp, resolver)).ToList (), - Flags = value.Flags + Flags = (KotlinAnnotationFlags)value.Flags }; } } @@ -159,7 +192,7 @@ internal static KotlinEffect FromProtobuf (Effect ef, JvmNameResolver resolver) public class KotlinExpression { - public int Flags { get; set; } + public KotlinExpressionFlags Flags { get; set; } public int ValueParameterReference { get; set; } public KotlinConstantValue ConstantValue { get; set; } public KotlinType IsInstanceType { get; set; } @@ -173,7 +206,7 @@ internal static KotlinExpression FromProtobuf (Expression exp, JvmNameResolver r return null; return new KotlinExpression { - Flags = exp.Flags, + Flags = (KotlinExpressionFlags)exp.Flags, ValueParameterReference = exp.ValueParameterReference, ConstantValue = (KotlinConstantValue) exp.constant_value, IsInstanceType = KotlinType.FromProtobuf (exp.IsInstanceType, resolver), @@ -184,19 +217,18 @@ internal static KotlinExpression FromProtobuf (Expression exp, JvmNameResolver r } } - public class KotlinFunction + public class KotlinFunction : KotlinMethodBase { - public int Flags { get; set; } public string Name { get; set; } + public KotlinFunctionFlags Flags { get; set; } public KotlinType ReturnType { get; set; } public int ReturnTypeId { get; set; } public List TypeParameters { get; set; } public KotlinType ReceiverType { get; set; } public int ReceiverTypeId { get; set; } - public List ValueParameters { get; set; } public KotlinTypeTable TypeTable { get; set; } - public int [] VersionRequirements { get; set; } public KotlinContract Contract { get; set; } + public List ValueParameters { get; set; } internal static KotlinFunction FromProtobuf (Function f, JvmNameResolver resolver) { @@ -204,7 +236,7 @@ internal static KotlinFunction FromProtobuf (Function f, JvmNameResolver resolve return null; return new KotlinFunction { - Flags = f.Flags, + Flags = (KotlinFunctionFlags)f.Flags, Name = resolver.GetString (f.Name), ReturnType = KotlinType.FromProtobuf (f.ReturnType, resolver), ReturnTypeId = f.ReturnTypeId, @@ -215,6 +247,20 @@ internal static KotlinFunction FromProtobuf (Function f, JvmNameResolver resolve VersionRequirements = f.VersionRequirements }; } + + public override string ToString () => Name; + + public string GetFlags () + { + var sb = new StringBuilder (); + + foreach (var f in Enum.GetNames (typeof (KotlinFunctionFlags))) { + if (Flags.HasFlag ((KotlinFunctionFlags)Enum.Parse (typeof (KotlinFunctionFlags), f))) + sb.Append (f); + } + + return sb.ToString (); + } } public class KotlinContract @@ -229,10 +275,10 @@ internal static KotlinContract FromProtobuf (Contract c, JvmNameResolver resolve } } - public class KotlinProperty + public class KotlinProperty : KotlinMethodBase { - public int Flags { get; set; } public string Name { get; set; } + public KotlinPropertyFlags Flags { get; set; } public KotlinType ReturnType { get; set; } public int ReturnTypeId { get; set; } public List TypeParameters { get; set; } @@ -241,7 +287,6 @@ public class KotlinProperty public KotlinValueParameter SetterValueParameter { get; set; } public int GetterFlags { get; set; } public int SetterFlags { get; set; } - public int [] VersionRequirements { get; set; } internal static KotlinProperty FromProtobuf (Property p, JvmNameResolver resolver) { @@ -249,7 +294,7 @@ internal static KotlinProperty FromProtobuf (Property p, JvmNameResolver resolve return null; return new KotlinProperty { - Flags = p.Flags, + Flags = (KotlinPropertyFlags)p.Flags, Name = resolver.GetString (p.Name), ReturnTypeId = p.ReturnTypeId, ReturnType = KotlinType.FromProtobuf (p.ReturnType, resolver), @@ -262,24 +307,26 @@ internal static KotlinProperty FromProtobuf (Property p, JvmNameResolver resolve VersionRequirements = p.VersionRequirements }; } + + public override string ToString () => Name; } public class KotlinType { public List Arguments { get; set; } public bool Nullable { get; set; } - public int FlexibleTypeCapabilitiesId { get; set; } + public int? FlexibleTypeCapabilitiesId { get; set; } public KotlinType FlexibleUpperBound { get; set; } public int FlexibleUpperBoundId { get; set; } public string ClassName { get; set; } - public int TypeParameter { get; set; } + public int? TypeParameter { get; set; } public string TypeParameterName { get; set; } public string TypeAliasName { get; set; } public KotlinType OuterType { get; set; } - public int OuterTypeId { get; set; } + public int? OuterTypeId { get; set; } public KotlinType AbbreviatedType { get; set; } - public int AbbreviatedTypeId { get; set; } - public int Flags { get; set; } + public int? AbbreviatedTypeId { get; set; } + public KotlinTypeFlags Flags { get; set; } internal static KotlinType FromProtobuf (Type t, JvmNameResolver resolver) { @@ -291,16 +338,21 @@ internal static KotlinType FromProtobuf (Type t, JvmNameResolver resolver) Nullable = t.Nullable, FlexibleTypeCapabilitiesId = t.FlexibleTypeCapabilitiesId, FlexibleUpperBound = FromProtobuf (t.FlexibleUpperBound, resolver), - ClassName = t.ClassName > 0 ? resolver.GetString (t.ClassName) : null, + ClassName = t.ClassName >= 0 ? resolver.GetString (t.ClassName.Value) : null, TypeParameter = t.TypeParameter, - TypeParameterName = t.TypeParameterName > 0 ? resolver.GetString (t.TypeParameterName) : null, + TypeParameterName = t.TypeParameterName >= 0 ? resolver.GetString (t.TypeParameterName.GetValueOrDefault ()) : null, OuterType = FromProtobuf (t.OuterType, resolver), OuterTypeId = t.OuterTypeId, AbbreviatedType = FromProtobuf (t.AbbreviatedType, resolver), AbbreviatedTypeId = t.AbbreviatedTypeId, - Flags = t.Flags + Flags = (KotlinTypeFlags)t.Flags }; } + + public string GetSignature () + { + return KotlinUtilities.ConvertKotlinTypeSignature (this); + } } public class KotlinTypeAlias @@ -436,7 +488,7 @@ internal static KotlinVersionRequirementTable FromProtobuf (VersionRequirementTa public class KotlinValueParameter { - public int Flags { get; set; } + public KotlinParameterFlags Flags { get; set; } public string Name { get; set; } public KotlinType Type { get; set; } public int TypeId { get; set; } @@ -449,7 +501,7 @@ internal static KotlinValueParameter FromProtobuf (ValueParameter vp, JvmNameRes return null; return new KotlinValueParameter { - Flags = vp.Flags, + Flags = (KotlinParameterFlags)vp.Flags, Name = resolver.GetString (vp.Name), Type = KotlinType.FromProtobuf (vp.Type, resolver), TypeId = vp.TypeId, @@ -457,6 +509,8 @@ internal static KotlinValueParameter FromProtobuf (ValueParameter vp, JvmNameRes VarArgElementTypeId = vp.VarargElementTypeId }; } + + public string GetSignature () => Type.GetSignature (); } public enum KotlinVariance @@ -531,6 +585,62 @@ public enum KotlinClassFlags { HasAnnotations = 0b00_00_000_1, + IsInner = 0b_00001_000_00_000_0, + IsData = 0b_00010_000_00_000_0, + IsExternalClass = 0b_00100_000_00_000_0, + IsExpectClass = 0b_01000_000_00_000_0, + IsInlineClass = 0b_10000_000_00_000_0 + } + + public enum KotlinClassVisibility + { + Internal = 0, + Private = 1, + Protected = 2, + Public = 3, + PrivateToThis = 4, + Local = 5 + } + + public enum KotlinClassType + { + Class = 0, + Interface = 1, + EnumClass = 2, + EnumEntry = 3, + AnnotationClass = 4, + Object = 5, + CompanionObject = 6 + } + + public enum KotlinClassInheritability + { + Final = 0, + Open = 1, + Abstract = 2, + Sealed = 3 + } + + [Flags] + public enum KotlinConstructorFlags + { + HasAnnotations = 0b0_000_1, + + Internal = 0b0_000_0, + Private = 0b0_001_0, + Protected = 0b0_010_0, + Public = 0b0_011_0, + PrivateToThis = 0b0_100_0, + Local = 0b0_101_0, + + IsSecondary = 0b1_000_0 + } + + [Flags] + public enum KotlinFunctionFlags + { + HasAnnotations = 0b00_00_000_1, + Internal = 0b00_00_000_0, Private = 0b00_00_001_0, Protected = 0b00_00_010_0, @@ -543,18 +653,101 @@ public enum KotlinClassFlags Abstract = 0b00_10_000_0, Sealed = 0b00_11_000_0, - Class = 0b000_00_000_0, - Interface = 0b001_00_000_0, - EnumClass = 0b010_00_000_0, - EnumEntry = 0b011_00_000_0, - AnnotationClass = 0b100_00_000_0, - Object = 0b101_00_000_0, - CompanionObject = 0b111_00_000_0, + Declaration = 0b00_00_000_0, + FakeOverride = 0b01_00_000_0, + Delegation = 0b10_00_000_0, + Synthesized = 0b11_00_000_0, + + IsOperator = 0b_0000001_00_00_000_0, + IsInfix = 0b_0000010_00_00_000_0, + IsInline = 0b_0000100_00_00_000_0, + IsTailrec = 0b_0001000_00_00_000_0, + IsExternalFunction = 0b_0010000_00_00_000_0, + IsSuspend = 0b_0100000_00_00_000_0, + IsExpectFunction = 0b_1000000_00_00_000_0 + } - IsInner = 0b_00001_000_00_000_0, - IsData = 0b_00010_000_00_000_0, - IsExternalClass = 0b_00100_000_00_000_0, - IsExpectClass = 0b_01000_000_00_000_0, - IsInlineClass = 0b_10000_000_00_000_0 + [Flags] + public enum KotlinPropertyFlags + { + HasAnnotations = 0b00_00_000_1, + + Internal = 0b00_00_000_0, + Private = 0b00_00_001_0, + Protected = 0b00_00_010_0, + Public = 0b00_00_011_0, + PrivateToThis = 0b00_00_100_0, + Local = 0b00_00_101_0, + + Final = 0b00_00_000_0, + Open = 0b00_01_000_0, + Abstract = 0b00_10_000_0, + Sealed = 0b00_11_000_0, + + Declaration = 0b00_00_000_0, + FakeOverride = 0b01_00_000_0, + Delegation = 0b10_00_000_0, + Synthesized = 0b11_00_000_0, + + IsVar = 0b_000000001_000_00_000_0, + HasGetter = 0b_000000010_000_00_000_0, + HasSetter = 0b_000000100_000_00_000_0, + IsConst = 0b_000001000_000_00_000_0, + IsLateInit = 0b_000010000_000_00_000_0, + HasConstant = 0b_000100000_000_00_000_0, + IsExternalProperty = 0b_001000000_000_00_000_0, + IsDelegated = 0b_010000000_000_00_000_0, + IsExpectProperty = 0b_100000000_000_00_000_0 + } + + [Flags] + public enum KotlinParameterFlags + { + HasAnnotations = 0b000_1, + + DeclaresDefaultValue = 0b001_0, + IsCrossInline = 0b010_0, + IsNoInline = 0b100_0 + } + + [Flags] + public enum KotlinAccessorFlags + { + HasAnnotations = 0b00_00_000_1, + + Internal = 0b00_00_000_0, + Private = 0b00_00_001_0, + Protected = 0b00_00_010_0, + Public = 0b00_00_011_0, + PrivateToThis = 0b00_00_100_0, + Local = 0b00_00_101_0, + + Final = 0b00_00_000_0, + Open = 0b00_01_000_0, + Abstract = 0b00_10_000_0, + Sealed = 0b00_11_000_0, + + IsNotDefault = 0b001_00_000_0, + IsExternalAccessor = 0b010_00_000_0, + IsInlineAccessor = 0b100_00_000_0 + } + + [Flags] + public enum KotlinExpressionFlags + { + IsNegated = 0b01, + IsNullCheckPredicate = 0b10 + } + + [Flags] + public enum KotlinAnnotationFlags + { + IsUnsigned = 0b01 + } + + [Flags] + public enum KotlinTypeFlags + { + SuspendType = 0b01 } } diff --git a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs index 1165a6f5a..c205c06e0 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Xamarin.Android.Tools.Bytecode.Kotlin +namespace Xamarin.Android.Tools.Bytecode { - static class KotlinFixups + public static class KotlinFixups { public static void Fixup (IList classes) { @@ -20,17 +21,39 @@ public static void Fixup (IList classes) try { var km = KotlinMetadata.FromAnnotation (kotlin); + var metadata = km.ParseMetadata (); - if (km.Kind == KotlinMetadataKind.Class) { - var class_metadata = km.AsClassMetadata (); + if (metadata is null) + continue; + // Do fixups only valid for full classes + var class_metadata = (metadata as KotlinClass); + + if (class_metadata != null) { FixupClassVisibility (c, class_metadata); - } else { - // We don't have explicit support for other types of Kotlin constructs yet, - // so they are unlikely to work. Mark them as private and consumers - // can override that if they want to fix them up. - c.AccessFlags = ClassAccessFlags.Private; + + if (!c.AccessFlags.IsPubliclyVisible ()) + continue; + + foreach (var con in class_metadata.Constructors) + FixupConstructor (FindJavaConstructor (class_metadata, con, c), con); + } + + // Do fixups valid for both classes and modules + // (We pass "class_metadata" even though it's sometimes null because it's + // used for generic type resolution if available for class types) + FixupJavaMethods (c.Methods); + + foreach (var met in metadata.Functions) + FixupFunction (FindJavaMethod (class_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); + + FixupProperty (getter, setter, prop); } + } catch (Exception ex) { Log.Warning (0, $"class-parse: warning: Unable to parse Kotlin metadata on '{c.ThisClass.Name}': {ex}"); } @@ -39,15 +62,208 @@ public static void Fixup (IList classes) static void FixupClassVisibility (ClassFile klass, KotlinClass metadata) { - // We don't have explicit support for these types of Kotlin constructs yet, - // so they are unlikely to work. Mark them as private and consumers - // can override that if they want to fix them up. - if (metadata.Flags.HasFlag (KotlinClassFlags.AnnotationClass) || metadata.Flags.HasFlag (KotlinClassFlags.CompanionObject) || metadata.Flags.HasFlag (KotlinClassFlags.Object)) - klass.AccessFlags = ClassAccessFlags.Private; - // Hide class if it isn't Public/Protected - if (!metadata.Flags.HasFlag (KotlinClassFlags.Public) && !metadata.Flags.HasFlag (KotlinClassFlags.Protected)) + if (klass.AccessFlags.IsPubliclyVisible () && !metadata.Visibility.IsPubliclyVisible ()) { + Log.Debug ($"Kotlin: Hiding internal class {klass.ThisClass.Name.Value}"); klass.AccessFlags = ClassAccessFlags.Private; + return; + } + } + + static void FixupJavaMethods (Methods methods) + { + // We do the following method level fixups here because we can operate on all methods, + // not just ones that have corresponding Kotlin metadata, like FixupFunction does. + + // Hide Kotlin generated methods like "add-impl" that aren't intended for end users + foreach (var method in methods.Where (m => m.IsPubliclyVisible && m.Name.Contains ("-impl"))) { + Log.Debug ($"Kotlin: Hiding implementation method {method.DeclaringType?.ThisClass.Name.Value} - {method.Name}"); + method.AccessFlags = MethodAccessFlags.Private; + } + + // Better parameter names in extension methods + foreach (var method in methods.Where (m => m.IsPubliclyVisible && m.AccessFlags.HasFlag (MethodAccessFlags.Static))) + FixupExtensionMethod (method); + } + + static void FixupConstructor (MethodInfo method, KotlinConstructor metadata) + { + if (method is null) + return; + + // Hide constructor if it isn't Public/Protected + if (method.IsPubliclyVisible && !metadata.Flags.IsPubliclyVisible ()) { + 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) + { + if (method is null || !method.IsPubliclyVisible) + return; + + // Hide function if it isn't Public/Protected + if (!metadata.Flags.IsPubliclyVisible ()) { + Log.Debug ($"Kotlin: Hiding internal method {method.DeclaringType?.ThisClass.Name.Value} - {metadata.GetSignature ()}"); + method.AccessFlags = MethodAccessFlags.Private; + 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]; + + 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; + } + } + } + + static void FixupExtensionMethod (MethodInfo method) + { + // Kotlin "extension" methods give the first parameter an ugly name + // like "$this$toByteString", we change it to "obj" to be a bit nicer. + var param = method.GetParameters (); + + if (param.Length > 0 && param [0].Name.StartsWith ("$this$")) { + Log.Debug ($"Kotlin: Renaming extension parameter {method.DeclaringType?.ThisClass.Name.Value} - {method.Name} - {param [0].Name} -> obj"); + param [0].Name = "obj"; + } + } + + static void FixupProperty (MethodInfo getter, MethodInfo setter, KotlinProperty metadata) + { + if (getter is null && setter is null) + return; + + // Hide property if it isn't Public/Protected + if (!metadata.Flags.IsPubliclyVisible ()) { + + if (getter?.IsPubliclyVisible == true) { + Log.Debug ($"Kotlin: Hiding internal getter method {getter.DeclaringType?.ThisClass.Name.Value} - {getter.Name}"); + getter.AccessFlags = MethodAccessFlags.Private; + } + + if (setter?.IsPubliclyVisible == true) { + Log.Debug ($"Kotlin: Hiding internal setter method {setter.DeclaringType?.ThisClass.Name.Value} - {setter.Name}"); + setter.AccessFlags = MethodAccessFlags.Private; + } + + return; + } + + if (setter != null) { + var setter_parameter = setter.GetParameters ().First (); + + if (setter_parameter.IsUnnamedParameter ()) { + Log.Debug ($"Kotlin: Renaming setter parameter {setter.DeclaringType?.ThisClass.Name.Value} - {setter.Name} - {setter_parameter.Name} -> value"); + setter_parameter.Name = "value"; + } + } + } + + static MethodInfo FindJavaConstructor (KotlinClass kotlinClass, KotlinConstructor constructor, ClassFile klass) + { + var all_constructors = klass.Methods.Where (method => method.Name == "" || method.Name == ""); + var possible_constructors = all_constructors.Where (method => method.GetFilteredParameters ().Length == constructor.ValueParameters.Count); + + foreach (var method in possible_constructors) { + if (ParametersMatch (kotlinClass, method, constructor.ValueParameters)) + return method; + } + + return null; + } + + static MethodInfo FindJavaMethod (KotlinClass kotlinClass, KotlinFunction function, ClassFile klass) + { + var possible_methods = klass.Methods.Where (method => method.GetMethodNameWithoutSuffix () == function.Name && + method.GetFilteredParameters ().Length == function.ValueParameters.Count); + + foreach (var method in possible_methods) { + if (!TypesMatch (method.ReturnType, function.ReturnType, kotlinClass)) + continue; + + if (!ParametersMatch (kotlinClass, method, function.ValueParameters)) + continue; + + return method; + } + + return null; + } + + static MethodInfo FindJavaPropertyGetter (KotlinClass 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) && + method.GetParameters ().Length == 0 && + TypesMatch (method.ReturnType, property.ReturnType, kotlinClass)); + + return possible_methods.FirstOrDefault (); + } + + static MethodInfo FindJavaPropertySetter (KotlinClass kotlinClass, KotlinProperty property, ClassFile klass) + { + var possible_methods = klass.Methods.Where (method => string.Compare (method.GetMethodNameWithoutSuffix (), $"set{property.Name}", true) == 0 && + property.ReturnType != null && + method.GetParameters ().Length == 1 && + TypesMatch (method.GetParameters () [0].Type, property.ReturnType, kotlinClass)); + + return possible_methods.FirstOrDefault (); + } + + static bool ParametersMatch (KotlinClass kotlinClass, MethodInfo method, List kotlinParameters) + { + var java_parameters = method.GetFilteredParameters (); + + if (java_parameters.Length == 0 && kotlinParameters.Count == 0) + return true; + + for (var i = 0; i < java_parameters.Length; i++) { + var java_p = java_parameters [i]; + var kotlin_p = kotlinParameters [i]; + + if (!TypesMatch (java_p.Type, kotlin_p.Type, kotlinClass)) + return false; + } + + return true; + } + + static bool TypesMatch (TypeInfo javaType, KotlinType kotlinType, KotlinClass kotlinClass) + { + // Generic type + if (!string.IsNullOrWhiteSpace (kotlinType.TypeParameterName) && $"T{kotlinType.TypeParameterName};" == javaType.TypeSignature) + return true; + + if (javaType.BinaryName == KotlinUtilities.ConvertKotlinTypeSignature (kotlinType, kotlinClass)) + return true; + + // Could be a generic type erasure + if (javaType.BinaryName == "Ljava/lang/Object;") + return true; + + // 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)) + 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)) + return true; + } + + return false; } } } diff --git a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinMetadata.cs b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinMetadata.cs index 30e84c1b2..cf397d15f 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinMetadata.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinMetadata.cs @@ -37,11 +37,38 @@ public static KotlinMetadata FromAnnotation (Annotation annotation) return km; } + public KotlinFile ParseMetadata () + { + switch (Kind) { + case KotlinMetadataKind.Class: + return AsClassMetadata (); + case KotlinMetadataKind.File: + return AsFileMetadata (); + default: + return null; + } + } + public KotlinClass AsClassMetadata () { if (Kind != KotlinMetadataKind.Class) return null; + var data = ParseStream (); + return KotlinClass.FromProtobuf (data.Item1, data.Item2); + } + + public KotlinFile AsFileMetadata () + { + if (Kind != KotlinMetadataKind.File) + return null; + + var data = ParseStream (); + return KotlinFile.FromProtobuf (data.Item1, data.Item2); + } + + Tuple ParseStream () + { var md = KotlinBitEncoding.DecodeBytes (Data1); using (var ms = ToMemoryStream (md)) { @@ -65,8 +92,8 @@ public KotlinClass AsClassMetadata () partial.MoveNext (); // Read the metadata structure from the stream - var metadata = Serializer.Deserialize (partial); - return KotlinClass.FromProtobuf (metadata, resolver); + var metadata = Serializer.Deserialize (partial); + return Tuple.Create (metadata, resolver); } } } @@ -87,7 +114,7 @@ static Version ParseVersion (Annotation annotation, string key) static string GetValue (Annotation annotation, string key) { - return (annotation.Values.FirstOrDefault (v => v.Key == key).Value as AnnotationElementConstant)?.Value; + return annotation.Values.FirstOrDefault (v => v.Key == key).Value?.ToString (); } static string[] GetValues (Annotation annotation, string key) diff --git a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinProtobufDefinition.cs b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinProtobufDefinition.cs index 16aa67176..ef91d97b0 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinProtobufDefinition.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinProtobufDefinition.cs @@ -254,8 +254,8 @@ public bool Nullable { private bool? __pbn__Nullable; [global::ProtoBuf.ProtoMember (4, Name = @"flexible_type_capabilities_id")] - public int FlexibleTypeCapabilitiesId { - get { return __pbn__FlexibleTypeCapabilitiesId.GetValueOrDefault (); } + public int? FlexibleTypeCapabilitiesId { + get { return __pbn__FlexibleTypeCapabilitiesId; } set { __pbn__FlexibleTypeCapabilitiesId = value; } } public bool ShouldSerializeFlexibleTypeCapabilitiesId () => __pbn__FlexibleTypeCapabilitiesId != null; @@ -266,8 +266,8 @@ public int FlexibleTypeCapabilitiesId { public Type FlexibleUpperBound { get; set; } [global::ProtoBuf.ProtoMember (8, Name = @"flexible_upper_bound_id")] - public int FlexibleUpperBoundId { - get { return __pbn__FlexibleUpperBoundId.GetValueOrDefault (); } + public int? FlexibleUpperBoundId { + get { return __pbn__FlexibleUpperBoundId; } set { __pbn__FlexibleUpperBoundId = value; } } public bool ShouldSerializeFlexibleUpperBoundId () => __pbn__FlexibleUpperBoundId != null; @@ -275,8 +275,8 @@ public int FlexibleUpperBoundId { private int? __pbn__FlexibleUpperBoundId; [global::ProtoBuf.ProtoMember (6, Name = @"class_name")] - public int ClassName { - get { return __pbn__ClassName.GetValueOrDefault (); } + public int? ClassName { + get { return __pbn__ClassName; } set { __pbn__ClassName = value; } } public bool ShouldSerializeClassName () => __pbn__ClassName != null; @@ -284,8 +284,8 @@ public int ClassName { private int? __pbn__ClassName; [global::ProtoBuf.ProtoMember (7, Name = @"type_parameter")] - public int TypeParameter { - get { return __pbn__TypeParameter.GetValueOrDefault (); } + public int? TypeParameter { + get { return __pbn__TypeParameter; } set { __pbn__TypeParameter = value; } } public bool ShouldSerializeTypeParameter () => __pbn__TypeParameter != null; @@ -293,8 +293,8 @@ public int TypeParameter { private int? __pbn__TypeParameter; [global::ProtoBuf.ProtoMember (9, Name = @"type_parameter_name")] - public int TypeParameterName { - get { return __pbn__TypeParameterName.GetValueOrDefault (); } + public int? TypeParameterName { + get { return __pbn__TypeParameterName; } set { __pbn__TypeParameterName = value; } } public bool ShouldSerializeTypeParameterName () => __pbn__TypeParameterName != null; @@ -302,8 +302,8 @@ public int TypeParameterName { private int? __pbn__TypeParameterName; [global::ProtoBuf.ProtoMember (12, Name = @"type_alias_name")] - public int TypeAliasName { - get { return __pbn__TypeAliasName.GetValueOrDefault (); } + public int? TypeAliasName { + get { return __pbn__TypeAliasName; } set { __pbn__TypeAliasName = value; } } public bool ShouldSerializeTypeAliasName () => __pbn__TypeAliasName != null; @@ -314,8 +314,8 @@ public int TypeAliasName { public Type OuterType { get; set; } [global::ProtoBuf.ProtoMember (11, Name = @"outer_type_id")] - public int OuterTypeId { - get { return __pbn__OuterTypeId.GetValueOrDefault (); } + public int? OuterTypeId { + get { return __pbn__OuterTypeId; } set { __pbn__OuterTypeId = value; } } public bool ShouldSerializeOuterTypeId () => __pbn__OuterTypeId != null; @@ -326,8 +326,8 @@ public int OuterTypeId { public Type AbbreviatedType { get; set; } [global::ProtoBuf.ProtoMember (14, Name = @"abbreviated_type_id")] - public int AbbreviatedTypeId { - get { return __pbn__AbbreviatedTypeId.GetValueOrDefault (); } + public int? AbbreviatedTypeId { + get { return __pbn__AbbreviatedTypeId; } set { __pbn__AbbreviatedTypeId = value; } } public bool ShouldSerializeAbbreviatedTypeId () => __pbn__AbbreviatedTypeId != null; diff --git a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinUtilities.cs b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinUtilities.cs new file mode 100644 index 000000000..3b3cabcc2 --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinUtilities.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Xamarin.Android.Tools.Bytecode +{ + public static class KotlinUtilities + { + public static string ConvertKotlinTypeSignature (KotlinType type, KotlinClass klass = null) + { + if (type is null) + return string.Empty; + + var class_name = type.ClassName; + + if (string.IsNullOrWhiteSpace (class_name)) { + if (klass is object) { + + var tp = klass.TypeParameters.FirstOrDefault (t => t.Id == type.TypeParameter); + + if (tp?.UpperBounds.FirstOrDefault ()?.ClassName != null) + return ConvertKotlinClassToJava (tp.UpperBounds.FirstOrDefault ()?.ClassName); + } + + return "Ljava/lang/Object;"; + } + + var result = ConvertKotlinClassToJava (class_name); + + if (result == "[") + result += ConvertKotlinTypeSignature (type.Arguments.FirstOrDefault ()?.Type); + + return result; + } + + public static string ConvertKotlinClassToJava (string className) + { + if (string.IsNullOrWhiteSpace (className)) + return className; + + className = className.Replace ('.', '$'); + + if (type_map.TryGetValue (className.TrimEnd (';'), out var result)) + return result; + + return "L" + className; + } + + public static string GetSignature (this List parameters) + { + return string.Join (string.Empty, parameters.Select (p => ConvertKotlinTypeSignature (p.Type))); + } + + public static ParameterInfo[] GetFilteredParameters (this MethodInfo method) + { + // Kotlin adds this to some constructors but I cannot tell which ones, + // so we'll just ignore them if we see them on the Java side + return method.GetParameters ().Where (p => p.Type.BinaryName != "Lkotlin/jvm/internal/DefaultConstructorMarker;" && !p.Name.StartsWith ("$")).ToArray (); + } + + public static string GetMethodNameWithoutSuffix (this MethodInfo method) + { + // Kotlin will rename some of its constructs to hide them from the Java runtime + // These take the form of thing like: + // - add-impl + // - add-H3FcsT8 + // We strip them for trying to match up the metadata to the MethodInfo + var index = method.Name.IndexOfAny (new [] { '-', '$' }); + + return index >= 0 ? method.Name.Substring (0, index) : method.Name; + } + + public static bool IsPubliclyVisible (this ClassAccessFlags flags) => flags.HasFlag (ClassAccessFlags.Public) || flags.HasFlag (ClassAccessFlags.Protected); + + public static bool IsPubliclyVisible (this KotlinClassVisibility flags) => flags == KotlinClassVisibility.Public || flags == KotlinClassVisibility.Protected; + + public static bool IsPubliclyVisible (this KotlinFunctionFlags flags) => flags.HasFlag (KotlinFunctionFlags.Public) || flags.HasFlag (KotlinFunctionFlags.Protected); + + public static bool IsPubliclyVisible (this KotlinConstructorFlags flags) => flags.HasFlag (KotlinConstructorFlags.Public) || flags.HasFlag (KotlinConstructorFlags.Protected); + + public static bool IsPubliclyVisible (this KotlinPropertyFlags flags) => flags.HasFlag (KotlinPropertyFlags.Public) || flags.HasFlag (KotlinPropertyFlags.Protected); + + public static bool IsUnnamedParameter (this ParameterInfo parameter) => parameter.Name.Length > 1 && parameter.Name.StartsWith ("p") && int.TryParse (parameter.Name.Substring (1), out var _); + + public static bool IsUnnamedParameter (this KotlinValueParameter parameter) => parameter.Name.Length > 1 && parameter.Name.StartsWith ("p") && int.TryParse (parameter.Name.Substring (1), out var _); + + static Dictionary type_map = new Dictionary { + { "kotlin/Int", "I" }, + { "kotlin/UInt", "I" }, + { "kotlin/Double", "D" }, + { "kotlin/Char", "C" }, + { "kotlin/Long", "J" }, + { "kotlin/ULong", "J" }, + { "kotlin/Float", "F" }, + { "kotlin/Short", "S" }, + { "kotlin/UShort", "S" }, + { "kotlin/Byte", "B" }, + { "kotlin/UByte", "B" }, + { "kotlin/Boolean", "Z" }, + { "kotlin/Unit", "V" }, + + { "kotlin/Array", "[" }, + { "kotlin/IntArray", "[I" }, + { "kotlin/UIntArray", "[I" }, + { "kotlin/DoubleArray", "[D" }, + { "kotlin/CharArray", "[C" }, + { "kotlin/LongArray", "[J" }, + { "kotlin/ULongArray", "[J" }, + { "kotlin/FloatArray", "[F" }, + { "kotlin/ShortArray", "[S" }, + { "kotlin/UShortArray", "[S" }, + { "kotlin/ByteArray", "[B" }, + { "kotlin/UByteArray", "[B" }, + { "kotlin/BooleanArray", "[Z" }, + + { "kotlin/Any", "Ljava/lang/Object;" }, + { "kotlin/Nothing", "Ljava/lang/Void;" }, + { "kotlin/Annotation", "Ljava/lang/annotation/Annotation;" }, + { "kotlin/String", "Ljava/lang/String;" }, + { "kotlin/CharSequence", "Ljava/lang/CharSequence;" }, + { "kotlin/Throwable", "Ljava/lang/Throwable;" }, + { "kotlin/Cloneable", "Ljava/lang/Cloneable;" }, + { "kotlin/Number", "Ljava/lang/Number;" }, + { "kotlin/Comparable", "Ljava/lang/Comparable;" }, + { "kotlin/Enum", "Ljava/lang/Enum;" }, + + { "kotlin/collections/Iterator", "Ljava/util/Iterator;" }, + { "kotlin/collections/MutableIterator", "Ljava/util/Iterator;" }, + { "kotlin/collections/Collection", "Ljava/util/Collection;" }, + { "kotlin/collections/MutableCollection", "Ljava/util/Collection;" }, + { "kotlin/collections/List", "Ljava/util/List;" }, + { "kotlin/collections/MutableList", "Ljava/util/List;" }, + { "kotlin/collections/Set", "Ljava/util/Set;" }, + { "kotlin/collections/MutableSet", "Ljava/util/Set;" }, + { "kotlin/collections/Map", "Ljava/util/Map;" }, + { "kotlin/collections/MutableMap", "Ljava/util/Map;" }, + { "kotlin/collections/ListIterator", "Ljava/util/ListIterator;" }, + { "kotlin/collections/MutableListIterator", "Ljava/util/ListIterator;" }, + + { "kotlin/collections/Iterable", "Ljava/lang/Iterable;" }, + { "kotlin/collections/MutableIterable", "Ljava/lang/Iterable;" }, + { "kotlin/collections/Map$Entry", "Ljava/util/Map$Entry;" }, + { "kotlin/collections/MutableMap$MutableEntry", "Ljava/util/Map$Entry;" }, + + { "kotlin/Function0", "Lkotlin/jvm/functions/Function0;" }, + { "kotlin/Function1", "Lkotlin/jvm/functions/Function1;" }, + { "kotlin/Function2", "Lkotlin/jvm/functions/Function2;" }, + { "kotlin/Function3", "Lkotlin/jvm/functions/Function3;" }, + { "kotlin/Function4", "Lkotlin/jvm/functions/Function4;" }, + { "kotlin/Function5", "Lkotlin/jvm/functions/Function5;" }, + { "kotlin/Function6", "Lkotlin/jvm/functions/Function6;" }, + { "kotlin/Function7", "Lkotlin/jvm/functions/Function7;" }, + { "kotlin/Function8", "Lkotlin/jvm/functions/Function8;" }, + { "kotlin/Function9", "Lkotlin/jvm/functions/Function9;" }, + { "kotlin/Function10", "Lkotlin/jvm/functions/Function10;" }, + { "kotlin/Function11", "Lkotlin/jvm/functions/Function11;" }, + { "kotlin/Function12", "Lkotlin/jvm/functions/Function12;" }, + { "kotlin/Function13", "Lkotlin/jvm/functions/Function13;" }, + { "kotlin/Function14", "Lkotlin/jvm/functions/Function14;" }, + { "kotlin/Function15", "Lkotlin/jvm/functions/Function15;" }, + { "kotlin/Function16", "Lkotlin/jvm/functions/Function16;" }, + { "kotlin/Function17", "Lkotlin/jvm/functions/Function17;" }, + { "kotlin/Function18", "Lkotlin/jvm/functions/Function18;" }, + { "kotlin/Function19", "Lkotlin/jvm/functions/Function19;" }, + { "kotlin/Function20", "Lkotlin/jvm/functions/Function20;" }, + { "kotlin/Function21", "Lkotlin/jvm/functions/Function21;" }, + { "kotlin/Function22", "Lkotlin/jvm/functions/Function22;" }, + }; + } +} diff --git a/src/Xamarin.Android.Tools.Bytecode/Methods.cs b/src/Xamarin.Android.Tools.Bytecode/Methods.cs index 1f727c79c..187138ef9 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Methods.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Methods.cs @@ -29,7 +29,7 @@ public sealed class MethodInfo { public ConstantPool ConstantPool {get; private set;} public ClassFile DeclaringType {get; private set;} - public MethodAccessFlags AccessFlags {get; private set;} + public MethodAccessFlags AccessFlags {get; set;} public AttributeCollection Attributes {get; private set;} public MethodInfo (ConstantPool constantPool, ClassFile declaringType, Stream stream) @@ -58,6 +58,8 @@ public bool IsConstructor { get {return Name == "";} } + public bool IsPubliclyVisible => AccessFlags.HasFlag (MethodAccessFlags.Public) || AccessFlags.HasFlag (MethodAccessFlags.Protected); + public TypeInfo ReturnType { get { int endParams; @@ -232,6 +234,8 @@ void UpdateParametersFromMethodParametersAttribute (ParameterInfo[] parameters) } } } + + public override string ToString () => Name; } public sealed class TypeInfo : IEquatable { diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/KotlinFixupsTests.cs b/src/Xamarin.Android.Tools.Bytecode/Tests/KotlinFixupsTests.cs new file mode 100644 index 000000000..2da9a3d35 --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/KotlinFixupsTests.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using Xamarin.Android.Tools.Bytecode; + +namespace Xamarin.Android.Tools.BytecodeTests +{ + [TestFixture] + public class KotlinFixupsTests : ClassFileFixture + { + [Test] + public void HideInternalClass () + { + var klass = LoadClassFile ("InternalClass.class"); + + Assert.True (klass.AccessFlags.HasFlag (ClassAccessFlags.Public)); + + KotlinFixups.Fixup (new [] { klass }); + + Assert.False (klass.AccessFlags.HasFlag (ClassAccessFlags.Public)); + } + + [Test] + public void HideInternalConstructor () + { + var klass = LoadClassFile ("InternalConstructor.class"); + var ctor = klass.Methods.First (m => m.Name == ""); + + Assert.True (ctor.AccessFlags.HasFlag (MethodAccessFlags.Public)); + + KotlinFixups.Fixup (new [] { klass }); + + Assert.False (ctor.AccessFlags.HasFlag (MethodAccessFlags.Public)); + } + + [Test] + public void HideImplementationMethod () + { + var klass = LoadClassFile ("MethodImplementation.class"); + var method = klass.Methods.First (m => m.Name == "toString-impl"); + + Assert.True (method.AccessFlags.HasFlag (MethodAccessFlags.Public)); + + KotlinFixups.Fixup (new [] { klass }); + + Assert.False (method.AccessFlags.HasFlag (MethodAccessFlags.Public)); + } + + [Test] + public void RenameExtensionParameter () + { + var klass = LoadClassFile ("RenameExtensionParameterKt.class"); + var method = klass.Methods.First (m => m.Name == "toUtf8String"); + var p = method.GetParameters () [0]; + + Assert.AreEqual ("$this$toUtf8String", p.Name); + + KotlinFixups.Fixup (new [] { klass }); + + Assert.AreEqual ("obj", p.Name); + } + + [Test] + public void HideInternalMethod () + { + var klass = LoadClassFile ("InternalMethod.class"); + var method = klass.Methods.First (m => m.Name == "take$main"); + + Assert.True (method.AccessFlags.HasFlag (MethodAccessFlags.Public)); + + KotlinFixups.Fixup (new [] { klass }); + + Assert.False (method.AccessFlags.HasFlag (MethodAccessFlags.Public)); + } + + [Test] + public void ParameterName () + { + var klass = LoadClassFile ("ParameterName.class"); + var method = klass.Methods.First (m => m.Name == "take"); + var p = method.GetParameters () [0]; + + Assert.AreEqual ("p0", p.Name); + + KotlinFixups.Fixup (new [] { klass }); + + Assert.AreEqual ("count", p.Name); + } + + [Test] + public void HideInternalProperty () + { + var klass = LoadClassFile ("InternalProperty.class"); + var getter = klass.Methods.First (m => m.Name == "getCity$main"); + var setter = klass.Methods.First (m => m.Name == "setCity$main"); + + Assert.True (getter.AccessFlags.HasFlag (MethodAccessFlags.Public)); + Assert.True (setter.AccessFlags.HasFlag (MethodAccessFlags.Public)); + + KotlinFixups.Fixup (new [] { klass }); + + Assert.False (getter.AccessFlags.HasFlag (MethodAccessFlags.Public)); + Assert.False (setter.AccessFlags.HasFlag (MethodAccessFlags.Public)); + } + + [Test] + public void RenameSetterParameter () + { + var klass = LoadClassFile ("SetterParameterName.class"); + var setter = klass.Methods.First (m => m.Name == "setCity"); + var p = setter.GetParameters () [0]; + + Assert.AreEqual ("p0", p.Name); + + KotlinFixups.Fixup (new [] { klass }); + + Assert.AreEqual ("value", p.Name); + } + } +} diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/KotlinMetadataTests.cs b/src/Xamarin.Android.Tools.Bytecode/Tests/KotlinMetadataTests.cs index 1a8da6a85..7126ac34f 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Tests/KotlinMetadataTests.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/KotlinMetadataTests.cs @@ -11,49 +11,197 @@ public class KotlinMetadataTests : ClassFileFixture [Test] public void PublicKotlinClassFile () { - var meta = GetMetadataForClassFile ("PublicClass.class"); + var klass_meta = GetClassMetadata ("PublicClass.class"); - Assert.AreEqual (KotlinMetadataKind.Class, meta.Kind); + Assert.AreEqual (KotlinClassVisibility.Public, klass_meta.Visibility); + Assert.AreEqual (KotlinClassInheritability.Final, klass_meta.Inheritability); + } - var klass_meta = meta.AsClassMetadata (); + [Test] + public void PrivateKotlinClassFile () + { + var klass_meta = GetClassMetadata ("PrivateClass.class"); - Assert.True (klass_meta.Flags.HasFlag (KotlinClassFlags.Public)); + Assert.AreEqual (KotlinClassVisibility.Private, klass_meta.Visibility); + Assert.AreEqual (KotlinClassInheritability.Final, klass_meta.Inheritability); } [Test] - public void PrivateKotlinClassFile () + public void InternalKotlinClassFile () { - var meta = GetMetadataForClassFile ("PrivateClass.class"); + var klass_meta = GetClassMetadata ("InternalClass.class"); - Assert.AreEqual (KotlinMetadataKind.Class, meta.Kind); + Assert.AreEqual (KotlinClassVisibility.Internal, klass_meta.Visibility); + Assert.AreEqual (KotlinClassInheritability.Final, klass_meta.Inheritability); + } - var klass_meta = meta.AsClassMetadata (); + [Test] + public void ProtectedKotlinClassFile () + { + var klass_meta = GetClassMetadata ("PublicClass$ProtectedClass.class"); - Assert.True (klass_meta.Flags.HasFlag (KotlinClassFlags.Private)); + Assert.AreEqual (KotlinClassVisibility.Protected, klass_meta.Visibility); + Assert.AreEqual (KotlinClassInheritability.Final, klass_meta.Inheritability); } [Test] - public void InternalKotlinClassFile () + public void SealedClassFile () { - var meta = GetMetadataForClassFile ("InternalClass.class"); + var klass_meta = GetClassMetadata ("SealedClass.class"); - Assert.AreEqual (KotlinMetadataKind.Class, meta.Kind); + Assert.AreEqual (KotlinClassInheritability.Sealed, klass_meta.Inheritability); + } - var klass_meta = meta.AsClassMetadata (); + [Test] + public void Interface () + { + var klass_meta = GetClassMetadata ("MyInterface.class"); - Assert.True (klass_meta.Flags.HasFlag (KotlinClassFlags.Internal)); + Assert.AreEqual (KotlinClassVisibility.Public, klass_meta.Visibility); + Assert.AreEqual (KotlinClassInheritability.Abstract, klass_meta.Inheritability); + Assert.AreEqual (KotlinClassType.Interface, klass_meta.ObjectType); + + Assert.AreEqual (2, klass_meta.Functions.Count); + Assert.AreEqual (2, klass_meta.Properties.Count); } [Test] - public void ProtectedKotlinClassFile () + public void InterfaceDefaultImpls () { - var meta = GetMetadataForClassFile ("PublicClass$ProtectedClass.class"); + var meta = GetMetadataForClassFile ("MyInterface$DefaultImpls.class"); - Assert.AreEqual (KotlinMetadataKind.Class, meta.Kind); + Assert.AreEqual (KotlinMetadataKind.SyntheticClass, meta.Kind); + + // TODO: We don't support SyntheticClass yet + } - var klass_meta = meta.AsClassMetadata (); + [Test] + public void InterfaceInheritingInterface () + { + var klass_meta = GetClassMetadata ("MyInterface2.class"); + + Assert.AreEqual (KotlinClassVisibility.Public, klass_meta.Visibility); + Assert.AreEqual (KotlinClassInheritability.Abstract, klass_meta.Inheritability); + Assert.AreEqual (KotlinClassType.Interface, klass_meta.ObjectType); + + Assert.AreEqual (0, klass_meta.Functions.Count); + Assert.AreEqual (2, klass_meta.Properties.Count); + Assert.AreEqual ("MyInterface;", klass_meta.SuperTypes [0].ClassName); + } + + [Test] + public void ClassInheritingInterface () + { + var klass_meta = GetClassMetadata ("MyInterfaceChild.class"); + + Assert.AreEqual (KotlinClassVisibility.Public, klass_meta.Visibility); + Assert.AreEqual (KotlinClassInheritability.Final, klass_meta.Inheritability); + + Assert.AreEqual (1, klass_meta.Functions.Count); + Assert.AreEqual (1, klass_meta.Properties.Count); + Assert.AreEqual ("MyInterface;", klass_meta.SuperTypes [0].ClassName); + } - Assert.True (klass_meta.Flags.HasFlag (KotlinClassFlags.Protected)); + [Test] + public void DataClass () + { + var klass_meta = GetClassMetadata ("DataClass.class"); + + Assert.AreEqual (KotlinClassVisibility.Public, klass_meta.Visibility); + Assert.True (klass_meta.Flags.HasFlag (KotlinClassFlags.IsData)); + Assert.AreEqual (KotlinClassInheritability.Final, klass_meta.Inheritability); + + Assert.AreEqual (1, klass_meta.Constructors.Count); + Assert.AreEqual (6, klass_meta.Functions.Count); + Assert.AreEqual (3, klass_meta.Properties.Count); + } + + [Test] + public void EnumClassFile () + { + var klass_meta = GetClassMetadata ("EnumClass.class"); + + Assert.AreEqual (KotlinClassVisibility.Public, klass_meta.Visibility); + Assert.AreEqual (KotlinClassType.EnumClass, klass_meta.ObjectType); + Assert.AreEqual (KotlinClassInheritability.Final, klass_meta.Inheritability); + + Assert.AreEqual (1, klass_meta.Constructors.Count); + Assert.AreEqual (4, klass_meta.EnumEntries.Count); + } + + [Test] + public void EnumClassWithInterfaces () + { + var klass_meta = GetClassMetadata ("EnumClassWithInterfaces.class"); + + Assert.AreEqual (KotlinClassVisibility.Public, klass_meta.Visibility); + Assert.AreEqual (KotlinClassType.EnumClass, klass_meta.ObjectType); + Assert.AreEqual (KotlinClassInheritability.Final, klass_meta.Inheritability); + + Assert.AreEqual (1, klass_meta.Constructors.Count); + Assert.AreEqual (1, klass_meta.Functions.Count); + Assert.AreEqual (2, klass_meta.EnumEntries.Count); + + Assert.AreEqual ("PLUS", klass_meta.EnumEntries [0]); + Assert.AreEqual ("TIMES", klass_meta.EnumEntries [1]); + + Assert.AreEqual (2, klass_meta.SuperTypes.Count); + + Assert.AreEqual ("kotlin/Enum", klass_meta.SuperTypes [0].ClassName); + Assert.AreEqual ("EnumClassWithInterfacesInterface;", klass_meta.SuperTypes [1].ClassName); + } + + [Test] + public void EnumClassWithFunction () + { + var klass_meta = GetClassMetadata ("EnumClassWithInterfaces$PLUS.class"); + + Assert.AreEqual (KotlinClassVisibility.Public, klass_meta.Visibility); + Assert.AreEqual (KotlinClassType.EnumEntry, klass_meta.ObjectType); + Assert.AreEqual (KotlinClassInheritability.Final, klass_meta.Inheritability); + + Assert.AreEqual (1, klass_meta.Functions.Count); + Assert.AreEqual (1, klass_meta.SuperTypes.Count); + + Assert.AreEqual ("EnumClassWithInterfaces;", klass_meta.SuperTypes [0].ClassName); + } + + [Test] + public void InlineClass () + { + var klass_meta = GetClassMetadata ("InlineClass.class"); + + Assert.AreEqual (KotlinClassInheritability.Final, klass_meta.Inheritability); + Assert.True (klass_meta.Flags.HasFlag (KotlinClassFlags.IsInlineClass)); + } + + [Test] + public void Object () + { + var klass_meta = GetClassMetadata ("Object.class"); + + Assert.AreEqual (KotlinClassInheritability.Final, klass_meta.Inheritability); + + // One would expect this to be "Object" but it doesn't seem to be + Assert.AreEqual (KotlinClassType.Class, klass_meta.ObjectType); + } + + [Test] + public void CompanionObject () + { + var klass_meta = GetClassMetadata ("CompanionObject$Companion.class"); + + Assert.AreEqual (KotlinClassInheritability.Final, klass_meta.Inheritability); + Assert.AreEqual (KotlinClassType.CompanionObject, klass_meta.ObjectType); + } + + [Test] + public void KotlinExtensionMethods () + { + var klass_meta = GetClassMetadata ("ExtensionMethods.class"); + + Assert.AreEqual (KotlinClassInheritability.Final, klass_meta.Inheritability); + Assert.AreEqual (KotlinClassVisibility.Public, klass_meta.Visibility); } private KotlinMetadata GetMetadataForClassFile (string file) @@ -68,6 +216,18 @@ private KotlinMetadata GetMetadataForClassFile (string file) return meta; } + + private KotlinClass GetClassMetadata (string file) + { + var meta = GetMetadataForClassFile (file); + + Assert.AreEqual (KotlinMetadataKind.Class, meta.Kind); + + Assert.AreEqual ("1.0.3", meta.ByteCodeVersion.ToString ()); + Assert.AreEqual ("1.1.15", meta.MetadataVersion.ToString ()); + + return meta.AsClassMetadata (); + } } } diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj b/src/Xamarin.Android.Tools.Bytecode/Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj index cab5425f0..caeff246d 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj @@ -52,6 +52,7 @@ + diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/Xamarin.Android.Tools.Bytecode-Tests.targets b/src/Xamarin.Android.Tools.Bytecode/Tests/Xamarin.Android.Tools.Bytecode-Tests.targets index b902b9075..fb3706e2e 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Tests/Xamarin.Android.Tools.Bytecode-Tests.targets +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/Xamarin.Android.Tools.Bytecode-Tests.targets @@ -27,7 +27,7 @@ Outputs="@(TestKotlinJar->'%(RecursiveDir)%(Filename).class')" Condition=" '$(KotlinCPath)' != '' "> \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/CompanionObject$Companion.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/CompanionObject$Companion.class new file mode 100644 index 000000000..d01d39fc6 Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/CompanionObject$Companion.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/CompanionObject.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/CompanionObject.class new file mode 100644 index 000000000..c380cd78a Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/CompanionObject.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/CompanionObject.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/CompanionObject.kt new file mode 100644 index 000000000..69f889861 --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/CompanionObject.kt @@ -0,0 +1,3 @@ +class CompanionObject { + companion object { } +} \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/DataClass.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/DataClass.class new file mode 100644 index 000000000..84aafabbb Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/DataClass.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/DataClass.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/DataClass.kt new file mode 100644 index 000000000..c75507a7f --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/DataClass.kt @@ -0,0 +1,3 @@ +data class DataClass(val name: String = "", val age: Int = 0) { + var weight: Int = 0 +} \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClass.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClass.class new file mode 100644 index 000000000..2a2f4701f Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClass.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClass.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClass.kt new file mode 100644 index 000000000..d8026e828 --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClass.kt @@ -0,0 +1,3 @@ +enum class EnumClass { + NORTH, SOUTH, WEST, EAST +} \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClassWithInterfaces$PLUS.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClassWithInterfaces$PLUS.class new file mode 100644 index 000000000..16e36be4b Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClassWithInterfaces$PLUS.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClassWithInterfaces$TIMES.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClassWithInterfaces$TIMES.class new file mode 100644 index 000000000..571111d3a Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClassWithInterfaces$TIMES.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClassWithInterfaces.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClassWithInterfaces.class new file mode 100644 index 000000000..da1b369c8 Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClassWithInterfaces.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClassWithInterfaces.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClassWithInterfaces.kt new file mode 100644 index 000000000..5b2efc376 --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClassWithInterfaces.kt @@ -0,0 +1,15 @@ +interface EnumClassWithInterfacesInterface { + fun apply(t: Int, u: Int) : Int + fun applyAsInt(t: Int, u: Int) : Int +} + +enum class EnumClassWithInterfaces : EnumClassWithInterfacesInterface { + PLUS { + override fun apply(t: Int, u: Int): Int = t + u + }, + TIMES { + override fun apply(t: Int, u: Int): Int = t * u + }; + + override fun applyAsInt(t: Int, u: Int) = apply(t, u) +} \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClassWithInterfacesInterface.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClassWithInterfacesInterface.class new file mode 100644 index 000000000..3b63f4fa4 Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/EnumClassWithInterfacesInterface.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/ExtensionMethods.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/ExtensionMethods.class new file mode 100644 index 000000000..4065ea2b8 Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/ExtensionMethods.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/ExtensionMethods.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/ExtensionMethods.kt new file mode 100644 index 000000000..4c6ac694e --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/ExtensionMethods.kt @@ -0,0 +1,7 @@ +class ExtensionMethods { + fun MutableList.swap(index1: Int, index2: Int) { + val tmp = this[index1] // 'this' corresponds to the list + this[index1] = this[index2] + this[index2] = tmp + } +} \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InlineClass.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InlineClass.class new file mode 100644 index 000000000..c3c2b48ae Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InlineClass.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InlineClass.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InlineClass.kt new file mode 100644 index 000000000..c137def4e --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InlineClass.kt @@ -0,0 +1 @@ +inline class InlineClass(val value: String) \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/Interfaces.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/Interfaces.kt new file mode 100644 index 000000000..d4a01df59 --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/Interfaces.kt @@ -0,0 +1,26 @@ +interface MyInterface { + fun bar() + + val prop: Int // abstract + + val propertyWithImplementation: String + get() = "foo" + + fun foo() { + print(prop) + } +} + +class MyInterfaceChild : MyInterface { + override val prop: Int = 29 + + override fun bar() { + // body + } +} + +interface MyInterface2 : MyInterface { + val value2 : Int + + override val prop: Int get() = 30 +} \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalConstructor.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalConstructor.class new file mode 100644 index 000000000..2e8406b54 Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalConstructor.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalConstructor.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalConstructor.kt new file mode 100644 index 000000000..a1a474467 --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalConstructor.kt @@ -0,0 +1 @@ +class InternalConstructor internal constructor (private var myInt : Int) \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalMethod.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalMethod.class new file mode 100644 index 000000000..a4edaafc8 Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalMethod.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalMethod.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalMethod.kt new file mode 100644 index 000000000..8ad206d17 --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalMethod.kt @@ -0,0 +1,5 @@ +class InternalMethod { + internal fun take() + { + } +} \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalProperty.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalProperty.class new file mode 100644 index 000000000..4b8dccef2 Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalProperty.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalProperty.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalProperty.kt new file mode 100644 index 000000000..45b4922b0 --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/InternalProperty.kt @@ -0,0 +1,3 @@ +class InternalProperty { + internal var city: String = "London" +} \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MethodImplementation.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MethodImplementation.class new file mode 100644 index 000000000..a046801a3 Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MethodImplementation.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MethodImplementation.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MethodImplementation.kt new file mode 100644 index 000000000..5e216da38 --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MethodImplementation.kt @@ -0,0 +1,7 @@ +public inline class MethodImplementation constructor(@PublishedApi internal val data: Short) : Comparable { + public override fun toString(): String = "woof" + + override operator fun compareTo(other: Short): Int { + return 0; + } +} \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MyInterface$DefaultImpls.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MyInterface$DefaultImpls.class new file mode 100644 index 000000000..e8c60b1dc Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MyInterface$DefaultImpls.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MyInterface.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MyInterface.class new file mode 100644 index 000000000..cecdbcd32 Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MyInterface.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MyInterface2$DefaultImpls.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MyInterface2$DefaultImpls.class new file mode 100644 index 000000000..66bbbb170 Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MyInterface2$DefaultImpls.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MyInterface2.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MyInterface2.class new file mode 100644 index 000000000..3f77774fc Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MyInterface2.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MyInterfaceChild.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MyInterfaceChild.class new file mode 100644 index 000000000..34521a364 Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/MyInterfaceChild.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/Object.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/Object.class new file mode 100644 index 000000000..0fd447986 Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/Object.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/Object.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/Object.kt new file mode 100644 index 000000000..fe0184116 --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/Object.kt @@ -0,0 +1 @@ +object Object { } \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/ParameterName.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/ParameterName.class new file mode 100644 index 000000000..0927cd7df Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/ParameterName.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/ParameterName.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/ParameterName.kt new file mode 100644 index 000000000..43fbf79dc --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/ParameterName.kt @@ -0,0 +1,3 @@ +interface ParameterName { + fun take(count: Int) +} \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/RenameExtensionParameter.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/RenameExtensionParameter.kt new file mode 100644 index 000000000..70f80bd1d --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/RenameExtensionParameter.kt @@ -0,0 +1 @@ +internal fun ByteArray.toUtf8String(): String = String(this, Charsets.UTF_8) \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/RenameExtensionParameterKt.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/RenameExtensionParameterKt.class new file mode 100644 index 000000000..00542d8f5 Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/RenameExtensionParameterKt.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/SealedClass.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/SealedClass.class new file mode 100644 index 000000000..0a0ff0789 Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/SealedClass.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/SealedClass.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/SealedClass.kt new file mode 100644 index 000000000..5fdb470d1 --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/SealedClass.kt @@ -0,0 +1 @@ +sealed class SealedClass \ No newline at end of file diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/SetterParameterName.class b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/SetterParameterName.class new file mode 100644 index 000000000..43822c967 Binary files /dev/null and b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/SetterParameterName.class differ diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/SetterParameterName.kt b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/SetterParameterName.kt new file mode 100644 index 000000000..7b261cd7b --- /dev/null +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/kotlin/SetterParameterName.kt @@ -0,0 +1,3 @@ +interface SetterParameterName { + var city: String +} \ No newline at end of file diff --git a/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs b/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs index 2cfe6097d..7756d8036 100644 --- a/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs +++ b/tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs @@ -243,8 +243,25 @@ public static Method CreateMethod (GenBase declaringType, XElement elem) if (elem.Attribute ("managedName") != null) method.Name = elem.XGetAttribute ("managedName"); - else - method.Name = StringRocks.MemberToPascalCase (method.JavaName); + else { + var name = method.JavaName; + + // Kotlin generates methods that cannot be referenced in Java, + // like `add-impl` and `add-V5j3Lk8`. We mangle them back into + // something a user would expect by truncating anything after the hyphen. + var index = name.IndexOf ("-impl"); + + if (index >= 0) + name = name.Substring (0, index); + + index = name.IndexOf ('-'); + + // `add-V5j3Lk8` is always a 7 character hashcode + if (index >= 0 && name.Length - index == 8) + name = name.Substring (0, index); + + method.Name = StringRocks.MemberToPascalCase (name); + } if (method.IsReturnEnumified) { method.ManagedReturn = elem.XGetAttribute ("enumReturn"); diff --git a/tools/generator/Tests/Unit-Tests/XmlApiImporterTests.cs b/tools/generator/Tests/Unit-Tests/XmlApiImporterTests.cs index c8161224a..a7681be26 100644 --- a/tools/generator/Tests/Unit-Tests/XmlApiImporterTests.cs +++ b/tools/generator/Tests/Unit-Tests/XmlApiImporterTests.cs @@ -89,6 +89,24 @@ public void CreateMethod_EnsureValidNameHyphen () Assert.AreEqual ("_3", klass.Methods [0].Name); } + [Test] + public void CreateMethod_EnsureKotlinImplFix () + { + var xml = XDocument.Parse (""); + var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class")); + + Assert.AreEqual ("Add", klass.Methods [0].Name); + } + + [Test] + public void CreateMethod_EnsureKotlinHashcodeFix () + { + var xml = XDocument.Parse (""); + var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class")); + + Assert.AreEqual ("Add", klass.Methods [0].Name); + } + [Test] public void CreateParameter_EnsureValidName () {