Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

Commit fadbb82

Browse files
authored
[generator] Add support for @explicitInterface metadata (#1006)
Fixes: #1005 Context: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/default-interface-methods#reabstraction Sometimes Java interfaces will "re-declare" methods from implemented interfaces: // Java interface /* java.lang. */ AutoCloseable { void close(); } interface /* java.io. */ Closeable { void close(); } Historically, this has been bound in C# by "ignoring" that the method was re-declared: // C# partial interface /* Java.Lang. */ IAutoCloseable { void Close(); } partial interface /* Java.IO. */ ICloseable : IAutoCloseable { void Close(); } This would result in a [CS0108 warning][0]: warning CS0108: 'ICloseable.Close()' hides inherited member 'IAutoCloseable.Close()'. Use the new keyword if hiding was intended. *Note*: in .NET for Android, this *particular* scenario doesn't happen, as [`metadata` is used to remove `AutoCloseable`][1]. This scenario happens for other methods, e.g.: warning CS0108: 'IChannel.Close()' hides inherited member 'ICloseable.Close()'. Consequently, the .NET for Android build ignores CS0108 warnings. This in turn means that e.g. `IChannel.Close()` is implicitly declared as `new`. With the introduction of [Default Interface Members in C# 8][2], it is possible to ["re-abstract"][3] a method declared in an implemented interface. However, in order to re-abstract a member, the member must be explicitly qualified: partial interface ICloseable : IAutoCloseable { abstract void IAutoCloseable.Close (); } The declaring type isn't something we have enough information to figure out automatically. Add support for `explicitInterface` metadata which allows the declaring interface to be explicitly specified. <attr path="/api/package[@name='java.io']/interface[@name='Closeable']/method[@name='close']" name="explicitInterface">IAutoCloseable</attr> Note this is not really a concept that `generator` "understands" it will simply output whatever is specified in this attribute. Thus the value is the *C#* interface name, not the *Java* interface name. For properties, placing `explicitInterface` on either the `getFoo` or `setFoo` methods will work. The getter value takes precedence if both are specified. This is implemented for both `interface` and `class` members, should there be other use-cases that can benefit from it. [0]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs0108 [1]: https://github.com/xamarin/xamarin-android/blob/59352f833846c563768f18dd72133be101b0ddc6/src/Mono.Android/metadata#L1393 [2]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/default-interface-methods [3]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/default-interface-methods#reabstraction
1 parent 3e4a3c4 commit fadbb82

10 files changed

Lines changed: 164 additions & 0 deletions

File tree

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

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,156 @@ public void InheritedInterfaceAsClass ()
352352

353353
Assert.Pass("WriteType did not NRE");
354354
}
355+
356+
[Test]
357+
publicvoidExplicitInterfaceMetadata_InterfaceMethod()
358+
{
359+
varxml=@"<api>
360+
<package name='java.lang' jni-name='java/lang'>
361+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
362+
</package>
363+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
364+
<interface abstract='false' 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;'>
365+
<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' explicitInterface='IRowCounter'></method>
366+
</interface>
367+
</package>
368+
</api>";
369+
370+
vargens=ParseApiDefinition(xml);
371+
variface=gens.Single(g =>g.Name=="IMyInterface");
372+
373+
generator.Context.ContextTypes.Push(iface);
374+
generator.WriteType(iface,string.Empty,newGenerationInfo("","","MyAssembly"));
375+
generator.Context.ContextTypes.Pop();
376+
377+
// Ensure explicit interface was written
378+
Assert.True(writer.ToString().Contains("int IRowCounter.CountAffectedRows ("),$"was: `{writer}`");
379+
}
380+
381+
[Test]
382+
publicvoidExplicitInterfaceMetadata_InterfaceProperty()
383+
{
384+
varxml=@"<api>
385+
<package name='java.lang' jni-name='java/lang'>
386+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
387+
</package>
388+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
389+
<interface abstract='false' 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;'>
390+
<method abstract='true' deprecated='not deprecated' final='false' name='getAge' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' explicitInterface='IHasAge'></method>
391+
</interface>
392+
</package>
393+
</api>";
394+
395+
vargens=ParseApiDefinition(xml);
396+
variface=gens.Single(g =>g.Name=="IMyInterface");
397+
398+
generator.Context.ContextTypes.Push(iface);
399+
generator.WriteType(iface,string.Empty,newGenerationInfo("","","MyAssembly"));
400+
generator.Context.ContextTypes.Pop();
401+
402+
// Ensure explicit interface was written
403+
Assert.True(writer.ToString().Contains("int IHasAge.Age {"),$"was: `{writer}`");
404+
}
405+
406+
[Test]
407+
publicvoidExplicitInterfaceMetadata_ClassMethod()
408+
{
409+
varxml=@"<api>
410+
<package name='java.lang' jni-name='java/lang'>
411+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
412+
</package>
413+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
414+
<class abstract='false' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyClass;'>
415+
<method abstract='false' 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' explicitInterface='IRowCounter'></method>
416+
</class>
417+
</package>
418+
</api>";
419+
420+
vargens=ParseApiDefinition(xml);
421+
variface=gens.Single(g =>g.Name=="MyClass");
422+
423+
generator.Context.ContextTypes.Push(iface);
424+
generator.WriteType(iface,string.Empty,newGenerationInfo("","","MyAssembly"));
425+
generator.Context.ContextTypes.Pop();
426+
427+
// Ensure explicit interface was written
428+
Assert.True(writer.ToString().Contains("int IRowCounter.CountAffectedRows ("),$"was: `{writer}`");
429+
}
430+
431+
[Test]
432+
publicvoidExplicitInterfaceMetadata_ClassProperty()
433+
{
434+
varxml=@"<api>
435+
<package name='java.lang' jni-name='java/lang'>
436+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
437+
</package>
438+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
439+
<class abstract='false' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyClass;'>
440+
<method abstract='false' deprecated='not deprecated' final='false' name='getAge' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' explicitInterface='IHasAge'></method>
441+
</class>
442+
</package>
443+
</api>";
444+
445+
vargens=ParseApiDefinition(xml);
446+
variface=gens.Single(g =>g.Name=="MyClass");
447+
448+
generator.Context.ContextTypes.Push(iface);
449+
generator.WriteType(iface,string.Empty,newGenerationInfo("","","MyAssembly"));
450+
generator.Context.ContextTypes.Pop();
451+
452+
// Ensure explicit interface was written
453+
Assert.True(writer.ToString().Contains("int IHasAge.Age {"),$"was: `{writer}`");
454+
}
455+
456+
[Test]
457+
publicvoidExplicitInterfaceMetadata_AbstractClassMethod()
458+
{
459+
varxml=@"<api>
460+
<package name='java.lang' jni-name='java/lang'>
461+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
462+
</package>
463+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
464+
<class abstract='false' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyClass;'>
465+
<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' explicitInterface='IRowCounter'></method>
466+
</class>
467+
</package>
468+
</api>";
469+
470+
vargens=ParseApiDefinition(xml);
471+
variface=gens.Single(g =>g.Name=="MyClass");
472+
473+
generator.Context.ContextTypes.Push(iface);
474+
generator.WriteType(iface,string.Empty,newGenerationInfo("","","MyAssembly"));
475+
generator.Context.ContextTypes.Pop();
476+
477+
// Ensure explicit interface was written
478+
Assert.True(writer.ToString().Contains("abstract int IRowCounter.CountAffectedRows ("),$"was: `{writer}`");
479+
}
480+
481+
[Test]
482+
publicvoidExplicitInterfaceMetadata_AbstractClassProperty()
483+
{
484+
varxml=@"<api>
485+
<package name='java.lang' jni-name='java/lang'>
486+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
487+
</package>
488+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
489+
<class abstract='false' deprecated='not deprecated' extends='java.lang.Object' extends-generic-aware='java.lang.Object' jni-extends='Ljava/lang/Object;' final='false' name='MyClass' static='false' visibility='public' jni-signature='Lcom/xamarin/android/MyClass;'>
490+
<method abstract='true' deprecated='not deprecated' final='false' name='getAge' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' explicitInterface='IHasAge'></method>
491+
</class>
492+
</package>
493+
</api>";
494+
495+
vargens=ParseApiDefinition(xml);
496+
variface=gens.Single(g =>g.Name=="MyClass");
497+
498+
generator.Context.ContextTypes.Push(iface);
499+
generator.WriteType(iface,string.Empty,newGenerationInfo("","","MyAssembly"));
500+
generator.Context.ContextTypes.Pop();
501+
502+
// Ensure explicit interface was written
503+
Assert.True(writer.ToString().Contains("abstract int IHasAge.Age {"),$"was: `{writer}`");
504+
}
355505
}
356506

357507
[TestFixture]

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ public static Method CreateMethod (GenBase declaringType, XElement elem, CodeGen
348348
ArgsType=elem.Attribute("argsType")?.Value,
349349
CustomAttributes=elem.XGetAttribute("customAttributes"),
350350
Deprecated=elem.Deprecated(),
351+
ExplicitInterface=elem.XGetAttribute("explicitInterface"),
351352
EventName=elem.Attribute("eventName")?.Value,
352353
GenerateAsyncWrapper=elem.Attribute("generateAsyncWrapper")!=null,
353354
GenerateDispatchingSetter=elem.Attribute("generateDispatchingSetter")!=null,

‎tools/generator/Java.Interop.Tools.Generator.ObjectModel/Method.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public Method (GenBase declaringType) : base (declaringType)
3333
publicboolReturnNotNull{get;set;}
3434
publicReturnValueRetVal{get;set;}
3535
publicintSourceApiLevel{get;set;}
36+
publicstringExplicitInterface{get;set;}
3637

3738
// it used to be private though...
3839
internalstringAdjustedName=>IsReturnCharSequence?Name+"Formatted":Name;

‎tools/generator/Java.Interop.Tools.Generator.ObjectModel/Property.cs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,7 @@ public void AutoDetectEnumifiedOverrideProperties (AncestorDescendantCache cache
5858
}
5959
}
6060
}
61+
62+
publicstringExplicitInterface=>Getter?.ExplicitInterface??Setter?.ExplicitInterface;
6163
}
6264
}

‎tools/generator/SourceWriters/BoundAbstractProperty.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class BoundAbstractProperty : PropertyWriter
1919
publicBoundAbstractProperty(GenBasegen,Propertyproperty,CodeGenerationOptionsopt)
2020
{
2121
Name=property.AdjustedName;
22+
ExplicitInterfaceImplementation=property.ExplicitInterface;
2223
PropertyType=newTypeReferenceWriter(opt.GetTypeReferenceName(property.Getter.RetVal));
2324

2425
SetVisibility(property.Getter.RetVal.IsGeneric?"protected":property.Getter.Visibility);

‎tools/generator/SourceWriters/BoundInterfaceMethodDeclaration.cs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public BoundInterfaceMethodDeclaration (Method method, string adapter, CodeGener
2222
this.opt=opt;
2323

2424
Name=method.AdjustedName;
25+
ExplicitInterfaceImplementation=method.ExplicitInterface;
26+
2527
ReturnType=newTypeReferenceWriter(opt.GetTypeReferenceName(method.RetVal));
2628
IsDeclaration=true;
2729

‎tools/generator/SourceWriters/BoundInterfacePropertyDeclaration.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class BoundInterfacePropertyDeclaration : PropertyWriter
1616
publicBoundInterfacePropertyDeclaration(GenBasegen,Propertyproperty,stringadapter,CodeGenerationOptionsopt)
1717
{
1818
Name=property.AdjustedName;
19+
ExplicitInterfaceImplementation=property.ExplicitInterface;
1920

2021
PropertyType=newTypeReferenceWriter(opt.GetTypeReferenceName(property));
2122
IsAutoProperty=true;

‎tools/generator/SourceWriters/BoundMethod.cs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ public BoundMethod (GenBase type, Method method, CodeGenerationOptions opt, bool
4848
if(is_explicit)
4949
ExplicitInterfaceImplementation=GetDeclaringTypeOfExplicitInterfaceMethod(method.OverriddenInterfaceMethod);
5050

51+
// Allow user to override our explicit interface logic
52+
if(method.ExplicitInterface.HasValue())
53+
ExplicitInterfaceImplementation=method.ExplicitInterface;
54+
5155
if((IsVirtual||!IsOverride)&&type.RequiresNew(method.AdjustedName,method))
5256
IsShadow=true;
5357

‎tools/generator/SourceWriters/BoundMethodAbstractDeclaration.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public BoundMethodAbstractDeclaration (GenBase gen, Method method, CodeGeneratio
3636
}
3737

3838
Name=method.AdjustedName;
39+
ExplicitInterfaceImplementation=method.ExplicitInterface;
3940

4041
IsAbstract=true;
4142
IsShadow=impl.RequiresNew(method.Name,method);

‎tools/generator/SourceWriters/BoundProperty.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class BoundProperty : PropertyWriter
1919
publicBoundProperty(GenBasegen,Propertyproperty,CodeGenerationOptionsopt,boolwithCallbacks=true,boolforceOverride=false)
2020
{
2121
Name=property.AdjustedName;
22+
ExplicitInterfaceImplementation=property.ExplicitInterface;
2223
PropertyType=newTypeReferenceWriter(opt.GetTypeReferenceName(property.Getter.RetVal));
2324

2425
SetVisibility(genisInterfaceGen?string.Empty:property.Getter.IsAbstract&&property.Getter.RetVal.IsGeneric?"protected":(property.Setter??property.Getter).Visibility);

0 commit comments

Comments
 (0)