Skip to content

Commit b656f7f

Browse files
authored
[generator] Add support for skipInterfaceMethods (#1265)
Context: #1264 Context: 73ebad2 Context: a7e09b7 Context: dotnet/android@00256b6 Consider [`java.util.List<E>`][0], which implements [`java.util.SequencedCollection<E>`][1] and provides an interface default method to implement `SequencedCollection<E>.reversed()`: // Java package java.util; public /* partial */ interface SequencedCollection<E> implements Collection<E> { SequencedCollection<E> reverse (); } public /* partial */ interface List<E> implements SequencedCollection<E> { default List<E> reversed() { … } } We then *bind* the above, along with some manual fixups in dotnet/android@00256b69: // C# bindings public partial interface ISequencedCollection { [Register ("reversed", "()Ljava/util/SequencedCollection;", "GetReversedHandler:Java.Util.ISequencedCollectionInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", ApiSince = 35)] ISequencedCollection? Reversed (); } public partial interface IList : ISequencedCollection { [Register ("reversed", "()Ljava/util/List;", "GetReversedHandler:Java.Util.IList, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", ApiSince = 35)] unsafe Java.Util.ISequencedCollection Java.Util.ISequencedCollection.Reversed () => … } Next, we try to bind [`androidx.paging:paging-common-android`][2]. [`androidx.paging.ItemSnapshotList`][3] inherits from [`kotlin.collections.AbstractList`][4]: // Kotlin package kotlin.collections; public abstract /* partial */ class AbstractList<out E> protected constructor() : kotlin.collections.AbstractCollection<E>(), java.util.List<E> { } In a scenario that rhymes with 73ebad2, `generator` with `Mono.Android.dll` (bindings for `Java.Util.IList`) cannot determine that the `Kotlin.Collections.AbstractList` type has a `Reversed()` method (via `Java.Util.IList`), and so re-declares it: // C# public partial class AbstractList : Kotlin.Collections.AbstractCollection, Java.Util.IList { public abstract ISequencedCollection Reversed (); } We fear that a complete fix for this may be complicated, as we would have to determine if each interface method has a compatible default interface method on any other interface in scope. Introduce new `skipInterfaceMethods` metadata, with identical semantics to `skipInvokerMethods` (73ebad2), to allow specifying `interface`-derived methods which should be *skipped* when generating bindings: <attr path="/api/package[@name='kotlin.collections']/class[@name='AbstractList']" name="skipInterfaceMethods"> java/util/SequencedCollection.reversed()Ljava/util/SequencedCollection; </attr> The above fragment would prevent `Reversed()` from being declared in the binding for `Kotlin.Collections.AbstractList`. [0]: https://developer.android.com/reference/java/util/List [1]: https://developer.android.com/reference/java/util/SequencedCollection [2]: https://maven.google.com/web/index.html?q=paging#androidx.paging:paging-common-android [3]: https://developer.android.com/reference/androidx/paging/ItemSnapshotList [4]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-abstract-list/
1 parent 9d99723 commit b656f7f

File tree

4 files changed

+37
-0
lines changed

4 files changed

+37
-0
lines changed

tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs

+28
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,34 @@ public void SkipInterfaceInvokerMethodsMetadata ()
379379
Assert.False (writer.ToString ().Contains ("InvokeAbstractInt32Method"), $"was: `{writer}`");
380380
}
381381

382+
[Test]
383+
public void SkipInterfaceMethodsMetadata ()
384+
{
385+
var xml = @"<api>
386+
<package name='java.lang' jni-name='java/lang'>
387+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
388+
</package>
389+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
390+
<interface abstract='true' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyInterface' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyInterface;'>
391+
<method abstract='true' deprecated='not deprecated' final='false' name='countAffectedRows' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public'></method>
392+
</interface>
393+
<class abstract='true' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyBaseClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyBaseClass;' skipInterfaceMethods='com/xamarin/android/MyInterface.countAffectedRows()I'>
394+
<implements name='com.xamarin.android.MyInterface' name-generic-aware='com.xamarin.android.MyInterface' jni-type='Lcom/xamarin/android/MyInterface;'></implements>
395+
</class>
396+
</package>
397+
</api>";
398+
399+
var gens = ParseApiDefinition (xml);
400+
var klass = gens.Single (g => g.Name == "MyBaseClass");
401+
402+
generator.Context.ContextTypes.Push (klass);
403+
generator.WriteType (klass, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
404+
generator.Context.ContextTypes.Pop ();
405+
406+
// The abstract method should not get generated because we are suppressing it with 'skipInterfaceMethods'
407+
Assert.False (writer.ToString ().Contains ("public abstract int CountAffectedRows ();"), $"was: `{writer}`");
408+
}
409+
382410
[Test]
383411
public void CompatVirtualMethod_Class ()
384412
{

tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs

+4
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ public static ClassGen CreateClass (XElement pkg, XElement elem, CodeGenerationO
147147
}
148148
}
149149

150+
if (elem.Attribute ("skipInterfaceMethods")?.Value is string skip)
151+
foreach (var m in skip.Split (new char [] { ',', ' ', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries))
152+
klass.SkippedInterfaceMethods.Add (m);
153+
150154
return klass;
151155
}
152156

tools/generator/Java.Interop.Tools.Generator.ObjectModel/ClassGen.cs

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace MonoDroid.Generation
1111
public class ClassGen : GenBase
1212
{
1313
bool fill_explicit_implementation_started;
14+
HashSet<string> skipped_interface_methods;
1415

1516
public List<Ctor> Ctors { get; private set; } = new List<Ctor> ();
1617

@@ -355,6 +356,8 @@ public override void ResetValidation ()
355356
base.ResetValidation ();
356357
}
357358

359+
public HashSet<string> SkippedInterfaceMethods => skipped_interface_methods ??= new HashSet<string> ();
360+
358361
public override string ToNative (CodeGenerationOptions opt, string varname, Dictionary<string, string> mappings = null)
359362
{
360363
if (opt.CodeGenerationTarget == CodeGenerationTarget.JavaInterop1) {

tools/generator/SourceWriters/BoundClass.cs

+2
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ void AddInterfaceAbstractMembers (ClassGen klass, InterfaceGen iface, CodeGenera
221221

222222
if (context.ContextGeneratedMethods.Any (_ => _.Name == method.Name && _.JniSignature == method.JniSignature))
223223
continue;
224+
if (klass.SkippedInterfaceMethods.Contains (method.GetSkipInvokerSignature ()))
225+
continue;
224226

225227
for (var cls = klass; cls != null; cls = cls.BaseGen)
226228
if (cls.ContainsMethod (method, false) || cls != klass && klass.ExplicitlyImplementedInterfaceMethods.Contains (sig)) {

0 commit comments

Comments
 (0)