Skip to content

Commit 22d5687

Browse files
authored
[generator] Add @managedOverride values none and reabstract. (#1000)
Fixes: #981 Context: 5a0e37e Add support for `//class/method[@managedOverride = 'none']` and `//interface/method[@managedOverride = 'reabstract']`. Setting `@managedOverride` to `none` ensures that the member is not marked with `virtual` or `override`. This is useful for `sealed` classes, to avoid a [CS0549 error][0]. Previously, given the Java: // Java public final class MyClass { public void doThing() {/* … */ } } The `class-parse` XML would specify that `MyClass.doThing()` was "virtual" -- `@abstract` is false, `@final` is false: <class name="MyClass …> <method abstract="false" final="false" name="doThing" return="void" … /> </class> This would result in the C# binding: // C# public sealed partial class MyClass { public virtual void DoThing() => … } which would error out with a CS0549: error CS0549: 'MyClass.DoThing()' is a new virtual member in sealed type 'MyClass' This can now be resolved by setting `@managedOverride` to `none`: <attr path="//class[@name='MyClass']/method[@name='doThing']" name="managedOverride">none</attr> which will result in the compilable binding: // C# public sealed partial class MyClass { public void DoThing() => … } Setting `@managedOverride` to `reabstract` is part of support for re-abstracting interface members; see also a65d6fb. Currently in `src/Java.Base`, we have Java: // Java public interface AnnotatedType { default AnnotatedType getAnnotatedOwnerType() {…} } public interface AnnotatedArrayType implements AnnotatedType { AnnotatedType getAnnotatedOwnerType(); // re-abstract interface default method } which results in the C# binding: // C# public partial interface IAnnotatedType { virtual IAnnotatedType? AnnotatedOwnerType { get => … } } public partial interface IAnnotatedArrayType : IAnnotatedType { IAnnotatedType? AnnotatedOwnerType { get; } // CS0108 } which results in a [warning CS0108][1]: warning CS0108: 'IAnnotatedArrayType.AnnotatedOwnerType' hides inherited member 'IAnnotatedType.AnnotatedOwnerType'. Use the new keyword if hiding was intended. Fixing this requires two steps. First, we can now set `//interface/method/@managedOverride` to `reabstract`: <attr path="//interface[@name='AnnotatedArrayType']/method[@name='getAnnotatedOwnerType']" name="managedOverride">reabstract</attr> What we *also* need to do is make the member *explicitly qualified*. This can be "forced" for *properties* by setting `@propertyName`: <attr path="//interface[@name='AnnotatedArrayType']/method[@name='getAnnotatedOwnerType']" name="propertyName">IAnnotatedType.AnnotatedOwnerType</attr> `@managedOverride` and `@propertyName` work together to emit: public partial interface IAnnotatedArrayType : IAnnotatedType { abstract IAnnotatedType? IAnnotatedType.AnnotatedOwnerType {get;} } The problem is that this combination probably breaks Android output, and it can't be used for *method* overrides, e.g. having `java.io.Closeable.close()` re-abstract `java.lang.AutoCloseable.close()`. TODO: complete the "interface reabstract" case, possibly via `//interface/method[@explicitInterface='ManagedInterfaceName']`: <attr path="//interface[@name='AnnotatedArrayType']/method[@name='getAnnotatedOwnerType']" name="explicitInterface">IAnnotatedType</attr> [0]: https://docs.microsoft.com/en-us/dotnet/csharp/misc/cs0549 [1]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs0108
1 parent 7f1d2d7 commit 22d5687

File tree

5 files changed

+114
-0
lines changed

5 files changed

+114
-0
lines changed

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

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,56 @@ public void ManagedOverrideMethod_Override ()
147147
Assert.True (writer.ToString ().Contains ("public override unsafe int DoStuff ()"), $"was: `{writer.ToString ()}`");
148148
}
149149

150+
[Test]
151+
public void ManagedOverrideMethod_None ()
152+
{
153+
var xml = @"<api>
154+
<package name='java.lang' jni-name='java/lang'>
155+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
156+
</package>
157+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
158+
<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;'>
159+
<method abstract='false' deprecated='not deprecated' final='false' name='DoStuff' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' managedOverride='none'></method>
160+
</class>
161+
</package>
162+
</api>";
163+
164+
var gens = ParseApiDefinition (xml);
165+
var klass = gens.Single (g => g.Name == "MyClass");
166+
167+
generator.Context.ContextTypes.Push (klass);
168+
generator.WriteType (klass, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
169+
generator.Context.ContextTypes.Pop ();
170+
171+
// This would contain 'virtual' if the 'managedOverride' was not working
172+
Assert.True (writer.ToString ().Contains ("public unsafe int DoStuff ()"), $"was: `{writer}`");
173+
}
174+
175+
[Test]
176+
public void ManagedOverrideInterfaceMethod_Reabstract ()
177+
{
178+
var xml = @"<api>
179+
<package name='java.lang' jni-name='java/lang'>
180+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
181+
</package>
182+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
183+
<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;'>
184+
<method abstract='true' deprecated='not deprecated' final='false' name='DoStuff' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' managedOverride='reabstract'></method>
185+
</interface>
186+
</package>
187+
</api>";
188+
189+
var gens = ParseApiDefinition (xml);
190+
var iface = gens.Single (g => g.Name == "IMyInterface");
191+
192+
generator.Context.ContextTypes.Push (iface);
193+
generator.WriteType (iface, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
194+
generator.Context.ContextTypes.Pop ();
195+
196+
// This would not contain 'abstract' if the 'managedOverride' was not working
197+
Assert.True (writer.ToString ().Contains ("abstract int DoStuff ()"), $"was: `{writer}`");
198+
}
199+
150200
[Test]
151201
public void ManagedOverrideProperty_Virtual ()
152202
{
@@ -195,6 +245,56 @@ public void ManagedOverrideProperty_Override ()
195245
Assert.True (writer.ToString ().Contains ("public override unsafe int Name {"), $"was: `{writer.ToString ()}`");
196246
}
197247

248+
[Test]
249+
public void ManagedOverrideProperty_None ()
250+
{
251+
var xml = @"<api>
252+
<package name='java.lang' jni-name='java/lang'>
253+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
254+
</package>
255+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
256+
<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;'>
257+
<method abstract='false' deprecated='not deprecated' final='false' name='getName' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' managedOverride='none'></method>
258+
</class>
259+
</package>
260+
</api>";
261+
262+
var gens = ParseApiDefinition (xml);
263+
var klass = gens.Single (g => g.Name == "MyClass");
264+
265+
generator.Context.ContextTypes.Push (klass);
266+
generator.WriteType (klass, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
267+
generator.Context.ContextTypes.Pop ();
268+
269+
// This would contain 'virtual' if the 'managedOverride' was not working
270+
Assert.True (writer.ToString ().Contains ("public unsafe int Name {"), $"was: `{writer}`");
271+
}
272+
273+
[Test]
274+
public void ManagedOverrideInterfaceProperty_Reabstract ()
275+
{
276+
var xml = @"<api>
277+
<package name='java.lang' jni-name='java/lang'>
278+
<class abstract='false' deprecated='not deprecated' final='false' name='Object' static='false' visibility='public' jni-signature='Ljava/lang/Object;' />
279+
</package>
280+
<package name='com.xamarin.android' jni-name='com/xamarin/android'>
281+
<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;'>
282+
<method abstract='true' deprecated='not deprecated' final='false' name='getName' jni-signature='()I' bridge='false' native='false' return='int' jni-return='I' static='false' synchronized='false' synthetic='false' visibility='public' managedOverride='reabstract'></method>
283+
</interface>
284+
</package>
285+
</api>";
286+
287+
var gens = ParseApiDefinition (xml);
288+
var iface = gens.Single (g => g.Name == "IMyInterface");
289+
290+
generator.Context.ContextTypes.Push (iface);
291+
generator.WriteType (iface, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
292+
generator.Context.ContextTypes.Pop ();
293+
294+
// This would not contain 'abstract' if the 'managedOverride' was not working
295+
Assert.True (writer.ToString ().Contains ("abstract int Name {"), $"was: `{writer}`");
296+
}
297+
198298
[Test]
199299
public void WriteDuplicateInterfaceEventArgs ()
200300
{

tools/generator/SourceWriters/BoundInterfaceMethodDeclaration.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ public BoundInterfaceMethodDeclaration (Method method, string adapter, CodeGener
2525
ReturnType = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal));
2626
IsDeclaration = true;
2727

28+
// Allow user to force adding the 'abstract' keyword for "reabstraction"
29+
if (method.ManagedOverride?.ToLowerInvariant () == "reabstract")
30+
IsAbstract = true;
31+
2832
if (method.DeclaringType.IsGeneratable)
2933
Comments.Add ($"// Metadata.xml XPath method reference: path=\"{method.GetMetadataXPathReference (method.DeclaringType)}\"");
3034
if (method.Deprecated != null)

tools/generator/SourceWriters/BoundInterfacePropertyDeclaration.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ public BoundInterfacePropertyDeclaration (GenBase gen, Property property, string
2020
PropertyType = new TypeReferenceWriter (opt.GetTypeReferenceName (property));
2121
IsAutoProperty = true;
2222

23+
// Allow user to force adding the 'abstract' keyword for "reabstraction"
24+
if ((property.Getter ?? property.Setter).ManagedOverride?.ToLowerInvariant () == "reabstract")
25+
IsAbstract = true;
26+
2327
if (property.Getter != null) {
2428
HasGet = true;
2529

tools/generator/SourceWriters/BoundMethod.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ public BoundMethod (GenBase type, Method method, CodeGenerationOptions opt, bool
5858
} else if (method.ManagedOverride?.ToLowerInvariant () == "override") {
5959
IsVirtual = false;
6060
IsOverride = true;
61+
} else if (method.ManagedOverride?.ToLowerInvariant () == "none") {
62+
IsVirtual = false;
63+
IsOverride = false;
6164
}
6265

6366
ReturnType = new TypeReferenceWriter (opt.GetTypeReferenceName (method.RetVal));

tools/generator/SourceWriters/BoundProperty.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ public BoundProperty (GenBase gen, Property property, CodeGenerationOptions opt,
6666
} else if (!forceOverride && (property.Getter ?? property.Setter).ManagedOverride?.ToLowerInvariant () == "override") {
6767
IsVirtual = false;
6868
IsOverride = true;
69+
} else if (!forceOverride && (property.Getter ?? property.Setter).ManagedOverride?.ToLowerInvariant () == "none") {
70+
IsVirtual = false;
71+
IsOverride = false;
6972
}
7073

7174
// Unlike [Register], [Obsolete] cannot be put on property accessors, so we can apply them only under limited condition...

0 commit comments

Comments
 (0)