Skip to content

Commit 38c8a82

Browse files
authored
[Xamarin.Android.Tools.Bytecode] Kotlin unsigned internal props (#1156)
Context: 6bd7ae4 In commit 6bd7ae4, we updated the logic that matches a Kotlin public property getter/setter to a generated Java getter/setter that follows this pattern: // Kotlin public var type = 0 // Java private int type = 0; public int getType () { ... } public void setType (int p0) { ... } However, this caused unit tests in xamarin/xamarin-android to fail that when using Kotlin unsigned types: KotlinUnsignedTypesTests.cs: error CS0200: Property or indexer 'UnsignedInstanceMethods.UnsignedInstanceProperty' cannot be assigned to -- it is read only This is because properties that use Kotlin unsigned types append a `-<type-hash>` suffix to their getter/setter names: // Kotlin public var type: UInt = 0u // Java private int type = 0; public int getType-pVg5ArA () { ... } public void setType-WZ4Q5Ns (int p0) { ... } Update our Kotlin logic to handle this case.
1 parent 1adb796 commit 38c8a82

File tree

5 files changed

+46
-10
lines changed

5 files changed

+46
-10
lines changed

src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -389,10 +389,12 @@ static void FixupField (FieldInfo? field, KotlinProperty metadata)
389389
return null;
390390

391391
// Public/protected getters look like "getFoo"
392+
// Public/protected getters with unsigned types look like "getFoo-abcdefg"
392393
// Internal getters look like "getFoo$main"
394+
// Internal getters with unsigned types look like "getFoo-WZ4Q5Ns$main"
393395
var possible_methods = property.IsInternalVisibility ?
394-
klass.Methods.Where (method => method.Name.StartsWith ($"get{property.Name.Capitalize ()}$", StringComparison.Ordinal)) :
395-
klass.Methods.Where (method => method.Name.Equals ($"get{property.Name.Capitalize ()}", StringComparison.Ordinal));
396+
klass.Methods.Where (method => method.GetMethodNameWithoutUnsignedSuffix ().StartsWith ($"get{property.Name.Capitalize ()}$", StringComparison.Ordinal)) :
397+
klass.Methods.Where (method => method.GetMethodNameWithoutUnsignedSuffix ().Equals ($"get{property.Name.Capitalize ()}", StringComparison.Ordinal));
396398

397399
possible_methods = possible_methods.Where (method =>
398400
method.GetParameters ().Length == 0 &&
@@ -409,10 +411,12 @@ static void FixupField (FieldInfo? field, KotlinProperty metadata)
409411
return null;
410412

411413
// Public/protected setters look like "setFoo"
414+
// Public/protected setters with unsigned types look like "setFoo-abcdefg"
412415
// Internal setters look like "setFoo$main"
416+
// Internal setters with unsigned types look like "setFoo-WZ4Q5Ns$main"
413417
var possible_methods = property.IsInternalVisibility ?
414-
klass.Methods.Where (method => method.Name.StartsWith ($"set{property.Name.Capitalize ()}$", StringComparison.Ordinal)) :
415-
klass.Methods.Where (method => method.Name.Equals ($"set{property.Name.Capitalize ()}", StringComparison.Ordinal));
418+
klass.Methods.Where (method => method.GetMethodNameWithoutUnsignedSuffix ().StartsWith ($"set{property.Name.Capitalize ()}$", StringComparison.Ordinal)) :
419+
klass.Methods.Where (method => method.GetMethodNameWithoutUnsignedSuffix ().Equals ($"set{property.Name.Capitalize ()}", StringComparison.Ordinal));
416420

417421
possible_methods = possible_methods.Where (method =>
418422
property.ReturnType != null &&

src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinUtilities.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,23 @@ public static ParameterInfo[] GetFilteredParameters (this MethodInfo method)
7676
return method.GetParameters ().Where (p => p.Type.BinaryName != "Lkotlin/jvm/internal/DefaultConstructorMarker;" && !p.Name.StartsWith ("$", StringComparison.Ordinal)).ToArray ();
7777
}
7878

79-
public static string GetMethodNameWithoutSuffix (this MethodInfo method)
79+
public static string GetMethodNameWithoutUnsignedSuffix (this MethodInfo method)
80+
=> GetMethodNameWithoutUnsignedSuffix (method.Name);
81+
82+
public static string GetMethodNameWithoutUnsignedSuffix (string name)
8083
{
81-
// Kotlin will rename some of its constructs to hide them from the Java runtime
82-
// These take the form of thing like:
83-
// - add-impl
84+
// Kotlin will add a type hash suffix to the end of the method name that use unsigned types
8485
// - add-H3FcsT8
8586
// We strip them for trying to match up the metadata to the MethodInfo
86-
var index = method.Name.IndexOfAny (new [] { '-', '$' });
87+
// Additionally, generated setters for unsigned types have multiple suffixes,
88+
// we only want to remove the unsigned suffix.
89+
// - getFoo-pVg5ArA$main
90+
var dollar_index = name.IndexOf ('$');
91+
var dollar_suffix = dollar_index >= 0 ? name.Substring (dollar_index) : string.Empty;
92+
93+
var index = name.IndexOf ('-');
8794

88-
return index >= 0 ? method.Name.Substring (0, index) : method.Name;
95+
return index >= 0 ? name.Substring (0, index) + dollar_suffix : name;
8996
}
9097

9198
public static bool IsDefaultConstructorMarker (this MethodInfo method)

tests/Xamarin.Android.Tools.Bytecode-Tests/KotlinFixupsTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,17 @@ public void HandleKotlinNameShadowing ()
347347
Assert.False (klass.Methods.Single (m => m.Name == "getItype2$main").AccessFlags.HasFlag (MethodAccessFlags.Public));
348348
Assert.True (klass.Methods.Single (m => m.Name == "getItype2").AccessFlags.HasFlag (MethodAccessFlags.Public));
349349
Assert.True (klass.Methods.Single (m => m.Name == "setItype2").AccessFlags.HasFlag (MethodAccessFlags.Public));
350+
351+
// Internal property with unsigned type
352+
// Generated getter/setter should not be public
353+
Assert.False (klass.Methods.Single (m => m.Name == "getUnsignedInternalProperty-pVg5ArA$main").AccessFlags.HasFlag (MethodAccessFlags.Public));
354+
Assert.False (klass.Methods.Single (m => m.Name == "setUnsignedInternalProperty-WZ4Q5Ns$main").AccessFlags.HasFlag (MethodAccessFlags.Public));
355+
356+
// Public property with unsigned type
357+
// We want to check that KotlinType/KotlinReturnType are filled it as it proves our FindJavaProperty[Getter|Setter] functions are matching
358+
// (We aren't changing the visibility of the getter/setter, so we can't just check the access flags)
359+
Assert.AreEqual ("uint", klass.Methods.Single (m => m.Name == "getUnsignedPublicProperty-pVg5ArA").KotlinReturnType);
360+
Assert.AreEqual ("uint", klass.Methods.Single (m => m.Name == "setUnsignedPublicProperty-WZ4Q5Ns").GetParameters () [0].KotlinType);
350361
}
351362

352363
[Test]
@@ -418,5 +429,15 @@ public void MatchMetadataToMultipleMethodsWithSameMangledName ()
418429
Assert.AreEqual ("ubyte", java_methods.ElementAt (0).GetParameters ().Single (p => p.Name == "element").KotlinType);
419430
Assert.AreEqual ("ubyte", java_methods.ElementAt (1).GetParameters ().Single (p => p.Name == "element").KotlinType);
420431
}
432+
433+
[Test]
434+
public void GetMethodNameWithoutUnsignedSuffix ()
435+
{
436+
// Just a few quick tests to ensure the various cases are covered
437+
Assert.AreEqual ("setFoo", KotlinUtilities.GetMethodNameWithoutUnsignedSuffix ("setFoo"));
438+
Assert.AreEqual ("setFoo", KotlinUtilities.GetMethodNameWithoutUnsignedSuffix ("setFoo-7apg3OU"));
439+
Assert.AreEqual ("setFoo$main", KotlinUtilities.GetMethodNameWithoutUnsignedSuffix ("setFoo-7apg3OU$main"));
440+
Assert.AreEqual ("setFoo$main", KotlinUtilities.GetMethodNameWithoutUnsignedSuffix ("setFoo$main"));
441+
}
421442
}
422443
}
Binary file not shown.

tests/Xamarin.Android.Tools.Bytecode-Tests/kotlin/NameShadowing.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,8 @@ public class NameShadowing {
2727
internal val itype2 = 0
2828
fun getItype2(): Int = itype2
2929
fun setItype2(type: Int) { }
30+
31+
// Unsigned types properties
32+
internal var unsignedInternalProperty: UInt = 3u
33+
var unsignedPublicProperty: UInt = 3u
3034
}

0 commit comments

Comments
 (0)