diff --git a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs index 94f1435ad..56ded5131 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs @@ -389,10 +389,12 @@ static void FixupField (FieldInfo? field, KotlinProperty metadata) return null; // Public/protected getters look like "getFoo" + // Public/protected getters with unsigned types look like "getFoo-abcdefg" // Internal getters look like "getFoo$main" + // Internal getters with unsigned types look like "getFoo-WZ4Q5Ns$main" var possible_methods = property.IsInternalVisibility ? - klass.Methods.Where (method => method.Name.StartsWith ($"get{property.Name.Capitalize ()}$", StringComparison.Ordinal)) : - klass.Methods.Where (method => method.Name.Equals ($"get{property.Name.Capitalize ()}", StringComparison.Ordinal)); + klass.Methods.Where (method => method.GetMethodNameWithoutUnsignedSuffix ().StartsWith ($"get{property.Name.Capitalize ()}$", StringComparison.Ordinal)) : + klass.Methods.Where (method => method.GetMethodNameWithoutUnsignedSuffix ().Equals ($"get{property.Name.Capitalize ()}", StringComparison.Ordinal)); possible_methods = possible_methods.Where (method => method.GetParameters ().Length == 0 && @@ -409,10 +411,12 @@ static void FixupField (FieldInfo? field, KotlinProperty metadata) return null; // Public/protected setters look like "setFoo" + // Public/protected setters with unsigned types look like "setFoo-abcdefg" // Internal setters look like "setFoo$main" + // Internal setters with unsigned types look like "setFoo-WZ4Q5Ns$main" var possible_methods = property.IsInternalVisibility ? - klass.Methods.Where (method => method.Name.StartsWith ($"set{property.Name.Capitalize ()}$", StringComparison.Ordinal)) : - klass.Methods.Where (method => method.Name.Equals ($"set{property.Name.Capitalize ()}", StringComparison.Ordinal)); + klass.Methods.Where (method => method.GetMethodNameWithoutUnsignedSuffix ().StartsWith ($"set{property.Name.Capitalize ()}$", StringComparison.Ordinal)) : + klass.Methods.Where (method => method.GetMethodNameWithoutUnsignedSuffix ().Equals ($"set{property.Name.Capitalize ()}", StringComparison.Ordinal)); possible_methods = possible_methods.Where (method => property.ReturnType != null && diff --git a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinUtilities.cs b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinUtilities.cs index b9312c40e..bf68a8c8e 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinUtilities.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinUtilities.cs @@ -76,16 +76,23 @@ public static ParameterInfo[] GetFilteredParameters (this MethodInfo method) return method.GetParameters ().Where (p => p.Type.BinaryName != "Lkotlin/jvm/internal/DefaultConstructorMarker;" && !p.Name.StartsWith ("$", StringComparison.Ordinal)).ToArray (); } - public static string GetMethodNameWithoutSuffix (this MethodInfo method) + public static string GetMethodNameWithoutUnsignedSuffix (this MethodInfo method) + => GetMethodNameWithoutUnsignedSuffix (method.Name); + + public static string GetMethodNameWithoutUnsignedSuffix (string name) { - // Kotlin will rename some of its constructs to hide them from the Java runtime - // These take the form of thing like: - // - add-impl + // Kotlin will add a type hash suffix to the end of the method name that use unsigned types // - add-H3FcsT8 // We strip them for trying to match up the metadata to the MethodInfo - var index = method.Name.IndexOfAny (new [] { '-', '$' }); + // Additionally, generated setters for unsigned types have multiple suffixes, + // we only want to remove the unsigned suffix. + // - getFoo-pVg5ArA$main + var dollar_index = name.IndexOf ('$'); + var dollar_suffix = dollar_index >= 0 ? name.Substring (dollar_index) : string.Empty; + + var index = name.IndexOf ('-'); - return index >= 0 ? method.Name.Substring (0, index) : method.Name; + return index >= 0 ? name.Substring (0, index) + dollar_suffix : name; } public static bool IsDefaultConstructorMarker (this MethodInfo method) diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/KotlinFixupsTests.cs b/tests/Xamarin.Android.Tools.Bytecode-Tests/KotlinFixupsTests.cs index 1108e081a..292ea5f2a 100644 --- a/tests/Xamarin.Android.Tools.Bytecode-Tests/KotlinFixupsTests.cs +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/KotlinFixupsTests.cs @@ -347,6 +347,17 @@ public void HandleKotlinNameShadowing () Assert.False (klass.Methods.Single (m => m.Name == "getItype2$main").AccessFlags.HasFlag (MethodAccessFlags.Public)); Assert.True (klass.Methods.Single (m => m.Name == "getItype2").AccessFlags.HasFlag (MethodAccessFlags.Public)); Assert.True (klass.Methods.Single (m => m.Name == "setItype2").AccessFlags.HasFlag (MethodAccessFlags.Public)); + + // Internal property with unsigned type + // Generated getter/setter should not be public + Assert.False (klass.Methods.Single (m => m.Name == "getUnsignedInternalProperty-pVg5ArA$main").AccessFlags.HasFlag (MethodAccessFlags.Public)); + Assert.False (klass.Methods.Single (m => m.Name == "setUnsignedInternalProperty-WZ4Q5Ns$main").AccessFlags.HasFlag (MethodAccessFlags.Public)); + + // Public property with unsigned type + // We want to check that KotlinType/KotlinReturnType are filled it as it proves our FindJavaProperty[Getter|Setter] functions are matching + // (We aren't changing the visibility of the getter/setter, so we can't just check the access flags) + Assert.AreEqual ("uint", klass.Methods.Single (m => m.Name == "getUnsignedPublicProperty-pVg5ArA").KotlinReturnType); + Assert.AreEqual ("uint", klass.Methods.Single (m => m.Name == "setUnsignedPublicProperty-WZ4Q5Ns").GetParameters () [0].KotlinType); } [Test] @@ -418,5 +429,15 @@ public void MatchMetadataToMultipleMethodsWithSameMangledName () Assert.AreEqual ("ubyte", java_methods.ElementAt (0).GetParameters ().Single (p => p.Name == "element").KotlinType); Assert.AreEqual ("ubyte", java_methods.ElementAt (1).GetParameters ().Single (p => p.Name == "element").KotlinType); } + + [Test] + public void GetMethodNameWithoutUnsignedSuffix () + { + // Just a few quick tests to ensure the various cases are covered + Assert.AreEqual ("setFoo", KotlinUtilities.GetMethodNameWithoutUnsignedSuffix ("setFoo")); + Assert.AreEqual ("setFoo", KotlinUtilities.GetMethodNameWithoutUnsignedSuffix ("setFoo-7apg3OU")); + Assert.AreEqual ("setFoo$main", KotlinUtilities.GetMethodNameWithoutUnsignedSuffix ("setFoo-7apg3OU$main")); + Assert.AreEqual ("setFoo$main", KotlinUtilities.GetMethodNameWithoutUnsignedSuffix ("setFoo$main")); + } } } diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/kotlin/NameShadowing.class b/tests/Xamarin.Android.Tools.Bytecode-Tests/kotlin/NameShadowing.class index 989e48d09..023c36e06 100644 Binary files a/tests/Xamarin.Android.Tools.Bytecode-Tests/kotlin/NameShadowing.class and b/tests/Xamarin.Android.Tools.Bytecode-Tests/kotlin/NameShadowing.class differ diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/kotlin/NameShadowing.kt b/tests/Xamarin.Android.Tools.Bytecode-Tests/kotlin/NameShadowing.kt index 5c00497fc..93fcf3b34 100644 --- a/tests/Xamarin.Android.Tools.Bytecode-Tests/kotlin/NameShadowing.kt +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/kotlin/NameShadowing.kt @@ -27,4 +27,8 @@ public class NameShadowing { internal val itype2 = 0 fun getItype2(): Int = itype2 fun setItype2(type: Int) { } + + // Unsigned types properties + internal var unsignedInternalProperty: UInt = 3u + var unsignedPublicProperty: UInt = 3u } \ No newline at end of file