Skip to content

Commit 838e793

Browse files
committed
Support more Kotlin constructs.
1 parent 84ccb6d commit 838e793

File tree

5 files changed

+139
-48
lines changed

5 files changed

+139
-48
lines changed

src/Xamarin.Android.Tools.Bytecode/ClassFile.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ public bool IsStatic {
186186
public bool IsEnum {
187187
get {return (AccessFlags & ClassAccessFlags.Enum) != 0;}
188188
}
189+
190+
public override string ToString () => ThisClass?.Name.Value;
189191
}
190192

191193
[Flags]

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

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,39 @@
77

88
namespace Xamarin.Android.Tools.Bytecode
99
{
10-
public class KotlinClass
10+
public class KotlinFile
11+
{
12+
public List<KotlinFunction> Functions { get; set; }
13+
public List<KotlinProperty> Properties { get; set; }
14+
public List<KotlinTypeAlias> TypeAliases { get; set; }
15+
public KotlinTypeTable TypeTable { get; set; }
16+
public KotlinVersionRequirementTable VersionRequirementTable { get; set; }
17+
18+
internal static KotlinFile FromProtobuf (Package c, JvmNameResolver resolver)
19+
{
20+
return new KotlinFile {
21+
Functions = c.Functions?.Select (f => KotlinFunction.FromProtobuf (f, resolver)).ToList (),
22+
Properties = c.Properties?.Select (p => KotlinProperty.FromProtobuf (p, resolver)).ToList (),
23+
TypeAliases = c.TypeAlias?.Select (tp => KotlinTypeAlias.FromProtobuf (tp, resolver)).ToList (),
24+
TypeTable = KotlinTypeTable.FromProtobuf (c.TypeTable, resolver),
25+
VersionRequirementTable = KotlinVersionRequirementTable.FromProtobuf (c.VersionRequirementTable, resolver)
26+
};
27+
}
28+
}
29+
30+
public class KotlinClass : KotlinFile
1131
{
1232
public string CompanionObjectName { get; set; }
1333
public List<KotlinConstructor> Constructors { get; set; }
1434
public List<string> EnumEntries { get; set; }
1535
public KotlinClassFlags Flags { get; set; }
1636
public string FullyQualifiedName { get; set; }
17-
public List<KotlinFunction> Functions { get; set; }
1837
public List<string> NestedClassNames { get; set; } = new List<string> ();
19-
public List<KotlinProperty> Properties { get; set; }
2038
public List<string> SealedSubclassFullyQualifiedNames { get; set; }
2139
public List<string> SuperTypeIds { get; set; }
2240
public List<KotlinType> SuperTypes { get; set; }
23-
public List<KotlinTypeAlias> TypeAliases { get; set; }
2441
public List<KotlinTypeParameter> TypeParameters { get; set; }
25-
public KotlinTypeTable TypeTable { get; set; }
2642
public int [] VersionRequirements { get; set; }
27-
public KotlinVersionRequirementTable VersionRequirementTable { get; set; }
2843

2944
internal static KotlinClass FromProtobuf (Class c, JvmNameResolver resolver)
3045
{
@@ -228,6 +243,18 @@ internal static KotlinFunction FromProtobuf (Function f, JvmNameResolver resolve
228243
}
229244

230245
public override string ToString () => Name;
246+
247+
public string GetFlags ()
248+
{
249+
var sb = new StringBuilder ();
250+
251+
foreach (var f in Enum.GetNames (typeof (KotlinFunctionFlags))) {
252+
if (Flags.HasFlag ((KotlinFunctionFlags)Enum.Parse (typeof (KotlinFunctionFlags), f)))
253+
sb.Append (f);
254+
}
255+
256+
return sb.ToString ();
257+
}
231258
}
232259

233260
public class KotlinContract
@@ -614,13 +641,13 @@ public enum KotlinFunctionFlags
614641
Delegation = 0b10_00_000_0,
615642
Synthesized = 0b11_00_000_0,
616643

617-
IsOperator = 0b_0000001_000_00_000_0,
618-
IsInfix = 0b_0000010_000_00_000_0,
619-
IsInline = 0b_0000100_000_00_000_0,
620-
IsTailrec = 0b_0001000_000_00_000_0,
621-
IsExternalFunction = 0b_0010000_000_00_000_0,
622-
IsSuspend = 0b_0100000_000_00_000_0,
623-
IsExpectFunction = 0b_1000000_000_00_000_0
644+
IsOperator = 0b_0000001_00_00_000_0,
645+
IsInfix = 0b_0000010_00_00_000_0,
646+
IsInline = 0b_0000100_00_00_000_0,
647+
IsTailrec = 0b_0001000_00_00_000_0,
648+
IsExternalFunction = 0b_0010000_00_00_000_0,
649+
IsSuspend = 0b_0100000_00_00_000_0,
650+
IsExpectFunction = 0b_1000000_00_00_000_0
624651
}
625652

626653
[Flags]

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

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,32 +21,36 @@ public static void Fixup (IList<ClassFile> classes)
2121

2222
try {
2323
var km = KotlinMetadata.FromAnnotation (kotlin);
24+
var metadata = km.ParseMetadata ();
2425

25-
if (km.Kind == KotlinMetadataKind.Class) {
26-
var class_metadata = km.AsClassMetadata ();
26+
if (metadata is null)
27+
return;
2728

28-
FixupClassVisibility (c, class_metadata);
29+
// Do fixups only valid for full classes
30+
var class_metadata = (metadata as KotlinClass);
2931

30-
if (class_metadata.Flags.HasFlag (KotlinClassFlags.EnumClass))
31-
continue;
32+
if (class_metadata != null) {
33+
FixupClassVisibility (c, class_metadata);
3234

3335
foreach (var con in class_metadata.Constructors)
3436
FixupConstructor (FindJavaConstructor (class_metadata, con, c), con);
37+
}
3538

36-
foreach (var met in class_metadata.Functions)
37-
FixupFunction (FindJavaMethod (class_metadata, met, c), met, class_metadata);
39+
// Do fixups valid for both classes and modules
40+
// (We pass "class_metadata" even though it's sometimes null because it's
41+
// used for generic type resolution if available for class types)
42+
FixupJavaMethods (c.Methods);
3843

39-
foreach (var prop in class_metadata.Properties) {
40-
var getter = FindJavaPropertyGetter (class_metadata, prop, c);
41-
var setter = FindJavaPropertySetter (class_metadata, prop, c);
44+
foreach (var met in metadata.Functions)
45+
FixupFunction (FindJavaMethod (class_metadata, met, c), met, class_metadata);
4246

43-
FixupProperty (getter, setter, prop);
44-
}
45-
} else {
46-
// We don't have explicit support for other types of Kotlin constructs yet,
47-
// so they are unlikely to work. Mark them as private so they don't get bound.
48-
c.AccessFlags = ClassAccessFlags.Private;
47+
foreach (var prop in metadata.Properties) {
48+
var getter = FindJavaPropertyGetter (class_metadata, prop, c);
49+
var setter = FindJavaPropertySetter (class_metadata, prop, c);
50+
51+
FixupProperty (getter, setter, prop);
4952
}
53+
5054
} catch (Exception ex) {
5155
Log.Warning (0, $"class-parse: warning: Unable to parse Kotlin metadata on '{c.ThisClass.Name}': {ex}");
5256
}
@@ -61,25 +65,22 @@ static void FixupClassVisibility (ClassFile klass, KotlinClass metadata)
6165
klass.AccessFlags = ClassAccessFlags.Private;
6266
return;
6367
}
68+
}
6469

65-
// We don't have explicit support for these types of Kotlin constructs yet,
66-
// so they are unlikely to work. Mark them as private so they don't get bound.
67-
if (metadata.Flags.HasFlag (KotlinClassFlags.AnnotationClass) || metadata.Flags.HasFlag (KotlinClassFlags.CompanionObject) || metadata.Flags.HasFlag (KotlinClassFlags.Object)) {
68-
Log.Debug ($"Kotlin: Hiding unsupported class {klass.ThisClass.Name.Value} ({metadata.Flags})");
69-
klass.AccessFlags = ClassAccessFlags.Private;
70+
static void FixupJavaMethods (Methods methods)
71+
{
72+
// We do the following method level fixups here because we can operate on all methods,
73+
// not just ones that have corresponding Kotlin metadata, like FixupFunction does.
74+
75+
// Hide Kotlin generated methods like "add-impl" that aren't intended for end users
76+
foreach (var method in methods.Where (m => m.IsPubliclyVisible && m.Name.Contains ("-impl"))) {
77+
Log.Debug ($"Kotlin: Hiding implementation method {method.DeclaringType?.ThisClass.Name.Value} - {method.Name}");
78+
method.AccessFlags = MethodAccessFlags.Private;
7079
}
7180

72-
foreach (var method in klass.Methods)
73-
if (method.Name.Contains ("-impl")) {
74-
Log.Debug ($"Kotlin: Hiding implementation method {method.DeclaringType?.ThisClass.Name.Value} - {method.Name}");
75-
method.AccessFlags = MethodAccessFlags.Private;
76-
} else if (method.Name.Contains ("-deprecated")) {
77-
Log.Debug ($"Kotlin: Hiding deprecated method {method.DeclaringType?.ThisClass.Name.Value} - {method.Name}");
78-
method.AccessFlags = MethodAccessFlags.Private;
79-
} else if (method.AccessFlags.HasFlag (MethodAccessFlags.Static) && method.GetParameters ().FirstOrDefault ()?.Name == "$this") {
80-
Log.Debug ($"Kotlin: Hiding extension method {method.DeclaringType?.ThisClass.Name.Value} - {method.Name}");
81-
method.AccessFlags = MethodAccessFlags.Private;
82-
}
81+
// Better parameter names in extension methods
82+
foreach (var method in methods.Where (m => m.IsPubliclyVisible && m.AccessFlags.HasFlag (MethodAccessFlags.Static)))
83+
FixupExtensionMethod (method);
8384
}
8485

8586
static void FixupConstructor (MethodInfo method, KotlinConstructor metadata)
@@ -97,16 +98,17 @@ static void FixupConstructor (MethodInfo method, KotlinConstructor metadata)
9798

9899
static void FixupFunction (MethodInfo method, KotlinFunction metadata, KotlinClass kotlinClass)
99100
{
100-
if (method is null)
101+
if (method is null || !method.IsPubliclyVisible)
101102
return;
102103

103104
// Hide function if it isn't Public/Protected
104-
if (method.IsPubliclyVisible && !metadata.Flags.IsPubliclyVisible ()) {
105+
if (!metadata.Flags.IsPubliclyVisible ()) {
105106
Log.Debug ($"Kotlin: Hiding internal method {method.DeclaringType?.ThisClass.Name.Value} - {metadata.GetSignature ()}");
106107
method.AccessFlags = MethodAccessFlags.Private;
107108
return;
108109
}
109110

111+
// Kotlin provides actual parameter names
110112
var java_parameters = method.GetFilteredParameters ();
111113

112114
for (var i = 0; i < java_parameters.Length; i++) {
@@ -120,6 +122,18 @@ static void FixupFunction (MethodInfo method, KotlinFunction metadata, KotlinCla
120122
}
121123
}
122124

125+
static void FixupExtensionMethod (MethodInfo method)
126+
{
127+
// Kotlin "extension" methods give the first parameter an ugly name
128+
// like "$this$toByteString", we change it to "obj" to be a bit nicer.
129+
var param = method.GetParameters ();
130+
131+
if (param.Length > 0 && param [0].Name.StartsWith ("$this$")) {
132+
Log.Debug ($"Kotlin: Renaming extension parameter {method.DeclaringType?.ThisClass.Name.Value} - {method.Name} - {param [0].Name} -> obj");
133+
param [0].Name = "obj";
134+
}
135+
}
136+
123137
static void FixupProperty (MethodInfo getter, MethodInfo setter, KotlinProperty metadata)
124138
{
125139
if (getter is null && setter is null)
@@ -142,7 +156,7 @@ static void FixupProperty (MethodInfo getter, MethodInfo setter, KotlinProperty
142156
}
143157

144158
if (setter != null) {
145-
var setter_parameter = setter.GetParameters () [0];
159+
var setter_parameter = setter.GetParameters ().First ();
146160

147161
if (setter_parameter.IsUnnamedParameter () && !metadata.SetterValueParameter.IsUnnamedParameter ()) {
148162
Log.Debug ($"Kotlin: Renaming setter parameter {setter.DeclaringType?.ThisClass.Name.Value} - {setter.Name} - {setter_parameter.Name} -> {metadata.SetterValueParameter.Name}");

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ public static KotlinMetadata FromAnnotation (Annotation annotation)
3737
return km;
3838
}
3939

40+
public KotlinFile ParseMetadata ()
41+
{
42+
switch (Kind) {
43+
case KotlinMetadataKind.Class:
44+
return AsClassMetadata ();
45+
case KotlinMetadataKind.File:
46+
return AsFileMetadata ();
47+
default:
48+
return null;
49+
}
50+
}
51+
4052
public KotlinClass AsClassMetadata ()
4153
{
4254
if (Kind != KotlinMetadataKind.Class)
@@ -71,6 +83,40 @@ public KotlinClass AsClassMetadata ()
7183
}
7284
}
7385

86+
public KotlinFile AsFileMetadata ()
87+
{
88+
if (Kind != KotlinMetadataKind.File)
89+
return null;
90+
91+
var md = KotlinBitEncoding.DecodeBytes (Data1);
92+
93+
using (var ms = ToMemoryStream (md)) {
94+
95+
// The first element is the length of the string table
96+
var first = ms.ReadByte ();
97+
98+
if (first == -1)
99+
return null;
100+
101+
ms.Position = 0;
102+
103+
var size = KotlinBitEncoding.ReadRawVarint32 (ms);
104+
105+
using (var partial = new PartialStream (ms, ms.Position, size)) {
106+
107+
// Read the string table from the stream
108+
var string_table = Serializer.Deserialize<org.jetbrains.kotlin.metadata.jvm.StringTableTypes> (partial);
109+
var resolver = new JvmNameResolver (string_table, Data2.ToList ());
110+
111+
partial.MoveNext ();
112+
113+
// Read the metadata structure from the stream
114+
var metadata = Serializer.Deserialize<org.jetbrains.kotlin.metadata.jvm.Package> (partial);
115+
return KotlinFile.FromProtobuf (metadata, resolver);
116+
}
117+
}
118+
}
119+
74120
static MemoryStream ToMemoryStream (byte [] bytes) => new MemoryStream (bytes);
75121

76122
static Version ParseVersion (Annotation annotation, string key)

src/Xamarin.Android.Tools.Bytecode/Methods.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ void UpdateParametersFromMethodParametersAttribute (ParameterInfo[] parameters)
234234
}
235235
}
236236
}
237+
238+
public override string ToString () => Name;
237239
}
238240

239241
public sealed class TypeInfo : IEquatable<TypeInfo> {

0 commit comments

Comments
 (0)