Skip to content

Commit bc31d27

Browse files
[generator] support covariant return types
Java has the ability to define a method, `Object getObject()` and then change the return type to `String getObject()` in an overridden method. This is *not* possible in C#, so cases like this cause the generator to create invalid C# code. My current thoughts to fix this are: - Look for “covariant return types” on interfaces and base classes via a `IsCovariantOverride` method - `IsCovariant` is held true if: neither are generic, have the same method name, have a different return type, and the signature of the parameters is the same. - If a covariant return value is found, replace the `RetVal` property with a clone of the base type’s return type - Include test cases for covariance, and hope we have enough test coverage to show if this breaks anything Other changes: - Removed unused `Method owner` parameter from `ReturnValue` type
1 parent 5b18729 commit bc31d27

22 files changed

+1401
-3
lines changed

tools/generator/GenBase.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,18 @@ bool ValidateMethod (CodeGenerationOptions opt, Method m)
415415
if (!m.Validate (opt, TypeParameters)) {
416416
return false;
417417
}
418+
419+
//Fix up Java covariant return types, C# return type must match the base return type
420+
var baseCovariantMethod = BaseGen?.methods.FirstOrDefault (bm => bm.IsCovariantOverride (m));
421+
if (baseCovariantMethod == null) {
422+
baseCovariantMethod = ifaces.OfType<InterfaceGen> ()
423+
.SelectMany (i => i.methods)
424+
.FirstOrDefault (im => im.IsCovariantOverride (m));
425+
}
426+
if (baseCovariantMethod != null) {
427+
m.RetVal = new ReturnValue (baseCovariantMethod.RetVal);
428+
}
429+
418430
return true;
419431
}
420432

tools/generator/Method.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ protected Method (GenBase declaringType, IMethodBaseSupport support)
262262

263263
internal void FillReturnType ()
264264
{
265-
retval = new ReturnValue (this, Return, ManagedReturn, IsReturnEnumified);
265+
retval = new ReturnValue (Return, ManagedReturn, IsReturnEnumified);
266266
}
267267

268268
public bool GenerateDispatchingSetter { get; protected set; }
@@ -381,6 +381,7 @@ public InterfaceGen ListenerType {
381381
ReturnValue retval;
382382
public ReturnValue RetVal {
383383
get { return retval; }
384+
set { retval = value; }
384385
}
385386

386387
public string ReturnType {
@@ -456,7 +457,22 @@ string GetDelegateType ()
456457
}
457458
return delegate_type;
458459
}
459-
460+
461+
public bool IsCovariantOverride (Method overriddenMethod)
462+
{
463+
//A method with a covariant return type would have the following to be true:
464+
// 1) Neither is generic
465+
// 2) JavaName is the same
466+
// 3) retval.JavaName is *different*
467+
// 4) Same number of parameters
468+
// 5) JavaSignature for parameters is the same
469+
return !IsGeneric && !overriddenMethod.IsGeneric &&
470+
JavaName == overriddenMethod.JavaName &&
471+
retval.JavaName != overriddenMethod.retval.JavaName &&
472+
Parameters.Count == overriddenMethod.Parameters.Count &&
473+
Parameters.JavaSignature == overriddenMethod.Parameters.JavaSignature;
474+
}
475+
460476
// it used to be private though...
461477
internal string AdjustedName {
462478
get { return IsReturnCharSequence ? Name + "Formatted" : Name; }

tools/generator/ReturnValue.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@ public class ReturnValue {
1515
string raw_type;
1616
bool is_enumified;
1717

18-
public ReturnValue (Method owner, string java_type, string managed_type, bool isEnumified)
18+
public ReturnValue (string java_type, string managed_type, bool isEnumified)
1919
{
2020
this.raw_type = this.java_type = java_type;
2121
this.managed_type = managed_type;
2222
this.is_enumified = isEnumified;
2323
}
2424

25+
public ReturnValue (ReturnValue value) : this (value.java_type, value.managed_type, value.is_enumified)
26+
{
27+
this.sym = value.sym;
28+
}
29+
2530
public string CallMethodPrefix {
2631
get {
2732
if (sym is SimpleSymbol || sym.IsEnum)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using NUnit.Framework;
3+
4+
namespace generatortests
5+
{
6+
[TestFixture]
7+
public class CovariantReturnTypes : BaseGeneratorTest
8+
{
9+
[Test]
10+
public void GeneratedOK ()
11+
{
12+
AllowWarnings = true;
13+
RunAllTargets (
14+
outputRelativePath: "CovariantReturnTypes",
15+
apiDescriptionFile: "expected/CovariantReturnTypes/CovariantReturnTypes.xml",
16+
expectedRelativePath: "CovariantReturnTypes");
17+
}
18+
}
19+
}
20+
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Android.Runtime;
4+
using Java.Interop;
5+
6+
namespace Covariant.Returntypes {
7+
8+
// Metadata.xml XPath class reference: path="/api/package[@name='covariant.returntypes']/class[@name='CovariantClass']"
9+
[global::Android.Runtime.Register ("covariant/returntypes/CovariantClass", DoNotGenerateAcw=true)]
10+
public partial class CovariantClass : global::Java.Lang.Object {
11+
12+
internal new static readonly JniPeerMembers _members = new JniPeerMembers ("covariant/returntypes/CovariantClass", typeof (CovariantClass));
13+
internal static new IntPtr class_ref {
14+
get {
15+
return _members.JniPeerType.PeerReference.Handle;
16+
}
17+
}
18+
19+
public override global::Java.Interop.JniPeerMembers JniPeerMembers {
20+
get { return _members; }
21+
}
22+
23+
protected override IntPtr ThresholdClass {
24+
get { return _members.JniPeerType.PeerReference.Handle; }
25+
}
26+
27+
protected override global::System.Type ThresholdType {
28+
get { return _members.ManagedPeerType; }
29+
}
30+
31+
protected CovariantClass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer) {}
32+
33+
static Delegate cb_getObject;
34+
#pragma warning disable 0169
35+
static Delegate GetGetObjectHandler ()
36+
{
37+
if (cb_getObject == null)
38+
cb_getObject = JNINativeWrapper.CreateDelegate ((Func<IntPtr, IntPtr, IntPtr>) n_GetObject);
39+
return cb_getObject;
40+
}
41+
42+
static IntPtr n_GetObject (IntPtr jnienv, IntPtr native__this)
43+
{
44+
global::Covariant.Returntypes.CovariantClass __this = global::Java.Lang.Object.GetObject<global::Covariant.Returntypes.CovariantClass> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);
45+
return JNIEnv.ToLocalJniHandle (__this.Object);
46+
}
47+
#pragma warning restore 0169
48+
49+
public virtual unsafe global::Java.Lang.Object Object {
50+
// Metadata.xml XPath method reference: path="/api/package[@name='covariant.returntypes']/class[@name='CovariantClass']/method[@name='getObject' and count(parameter)=0]"
51+
[Register ("getObject", "()Ljava/lang/Object;", "GetGetObjectHandler")]
52+
get {
53+
const string __id = "getObject.()Ljava/lang/Object;";
54+
try {
55+
var __rm = _members.InstanceMethods.InvokeVirtualObjectMethod (__id, this, null);
56+
return global::Java.Lang.Object.GetObject<global::Java.Lang.Object> (__rm.Handle, JniHandleOwnership.TransferLocalRef);
57+
} finally {
58+
}
59+
}
60+
}
61+
62+
}
63+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Android.Runtime;
4+
using Java.Interop;
5+
6+
namespace Covariant.Returntypes {
7+
8+
// Metadata.xml XPath class reference: path="/api/package[@name='covariant.returntypes']/class[@name='CovariantInterfaceImplementation']"
9+
[global::Android.Runtime.Register ("covariant/returntypes/CovariantInterfaceImplementation", DoNotGenerateAcw=true)]
10+
public partial class CovariantInterfaceImplementation : global::Java.Lang.Object, global::Covariant.Returntypes.ICovariantInterface {
11+
12+
internal new static readonly JniPeerMembers _members = new JniPeerMembers ("covariant/returntypes/CovariantInterfaceImplementation", typeof (CovariantInterfaceImplementation));
13+
internal static new IntPtr class_ref {
14+
get {
15+
return _members.JniPeerType.PeerReference.Handle;
16+
}
17+
}
18+
19+
public override global::Java.Interop.JniPeerMembers JniPeerMembers {
20+
get { return _members; }
21+
}
22+
23+
protected override IntPtr ThresholdClass {
24+
get { return _members.JniPeerType.PeerReference.Handle; }
25+
}
26+
27+
protected override global::System.Type ThresholdType {
28+
get { return _members.ManagedPeerType; }
29+
}
30+
31+
protected CovariantInterfaceImplementation (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer) {}
32+
33+
// Metadata.xml XPath constructor reference: path="/api/package[@name='covariant.returntypes']/class[@name='CovariantInterfaceImplementation']/constructor[@name='CovariantInterfaceImplementation' and count(parameter)=0]"
34+
[Register (".ctor", "()V", "")]
35+
public unsafe CovariantInterfaceImplementation ()
36+
: base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
37+
{
38+
const string __id = "()V";
39+
40+
if (((global::Java.Lang.Object) this).Handle != IntPtr.Zero)
41+
return;
42+
43+
try {
44+
var __r = _members.InstanceMethods.StartCreateInstance (__id, ((object) this).GetType (), null);
45+
SetHandle (__r.Handle, JniHandleOwnership.TransferLocalRef);
46+
_members.InstanceMethods.FinishCreateInstance (__id, this, null);
47+
} finally {
48+
}
49+
}
50+
51+
static Delegate cb_getObject;
52+
#pragma warning disable 0169
53+
static Delegate GetGetObjectHandler ()
54+
{
55+
if (cb_getObject == null)
56+
cb_getObject = JNINativeWrapper.CreateDelegate ((Func<IntPtr, IntPtr, IntPtr>) n_GetObject);
57+
return cb_getObject;
58+
}
59+
60+
static IntPtr n_GetObject (IntPtr jnienv, IntPtr native__this)
61+
{
62+
global::Covariant.Returntypes.CovariantInterfaceImplementation __this = global::Java.Lang.Object.GetObject<global::Covariant.Returntypes.CovariantInterfaceImplementation> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);
63+
return JNIEnv.ToLocalJniHandle (__this.Object);
64+
}
65+
#pragma warning restore 0169
66+
67+
public virtual unsafe global::Java.Lang.Object Object {
68+
// Metadata.xml XPath method reference: path="/api/package[@name='covariant.returntypes']/class[@name='CovariantInterfaceImplementation']/method[@name='getObject' and count(parameter)=0]"
69+
[Register ("getObject", "()Ljava/lang/Object;", "GetGetObjectHandler")]
70+
get {
71+
const string __id = "getObject.()Ljava/lang/Object;";
72+
try {
73+
var __rm = _members.InstanceMethods.InvokeVirtualObjectMethod (__id, this, null);
74+
return global::Java.Lang.Object.GetObject<global::Java.Lang.Object> (__rm.Handle, JniHandleOwnership.TransferLocalRef);
75+
} finally {
76+
}
77+
}
78+
}
79+
80+
}
81+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Android.Runtime;
4+
using Java.Interop;
5+
6+
namespace Covariant.Returntypes {
7+
8+
// Metadata.xml XPath class reference: path="/api/package[@name='covariant.returntypes']/class[@name='CovariantSubclass']"
9+
[global::Android.Runtime.Register ("covariant/returntypes/CovariantSubclass", DoNotGenerateAcw=true)]
10+
public partial class CovariantSubclass : global::Covariant.Returntypes.CovariantClass {
11+
12+
internal new static readonly JniPeerMembers _members = new JniPeerMembers ("covariant/returntypes/CovariantSubclass", typeof (CovariantSubclass));
13+
internal static new IntPtr class_ref {
14+
get {
15+
return _members.JniPeerType.PeerReference.Handle;
16+
}
17+
}
18+
19+
public override global::Java.Interop.JniPeerMembers JniPeerMembers {
20+
get { return _members; }
21+
}
22+
23+
protected override IntPtr ThresholdClass {
24+
get { return _members.JniPeerType.PeerReference.Handle; }
25+
}
26+
27+
protected override global::System.Type ThresholdType {
28+
get { return _members.ManagedPeerType; }
29+
}
30+
31+
protected CovariantSubclass (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer) {}
32+
33+
// Metadata.xml XPath constructor reference: path="/api/package[@name='covariant.returntypes']/class[@name='CovariantSubclass']/constructor[@name='CovariantSubclass' and count(parameter)=0]"
34+
[Register (".ctor", "()V", "")]
35+
public unsafe CovariantSubclass ()
36+
: base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
37+
{
38+
const string __id = "()V";
39+
40+
if (((global::Java.Lang.Object) this).Handle != IntPtr.Zero)
41+
return;
42+
43+
try {
44+
var __r = _members.InstanceMethods.StartCreateInstance (__id, ((object) this).GetType (), null);
45+
SetHandle (__r.Handle, JniHandleOwnership.TransferLocalRef);
46+
_members.InstanceMethods.FinishCreateInstance (__id, this, null);
47+
} finally {
48+
}
49+
}
50+
51+
static Delegate cb_getObject;
52+
#pragma warning disable 0169
53+
static Delegate GetGetObjectHandler ()
54+
{
55+
if (cb_getObject == null)
56+
cb_getObject = JNINativeWrapper.CreateDelegate ((Func<IntPtr, IntPtr, IntPtr>) n_GetObject);
57+
return cb_getObject;
58+
}
59+
60+
static IntPtr n_GetObject (IntPtr jnienv, IntPtr native__this)
61+
{
62+
global::Covariant.Returntypes.CovariantSubclass __this = global::Java.Lang.Object.GetObject<global::Covariant.Returntypes.CovariantSubclass> (jnienv, native__this, JniHandleOwnership.DoNotTransfer);
63+
return JNIEnv.ToLocalJniHandle (__this.Object);
64+
}
65+
#pragma warning restore 0169
66+
67+
public override unsafe global::Java.Lang.Object Object {
68+
// Metadata.xml XPath method reference: path="/api/package[@name='covariant.returntypes']/class[@name='CovariantSubclass']/method[@name='getObject' and count(parameter)=0]"
69+
[Register ("getObject", "()Ljava/lang/Object;", "GetGetObjectHandler")]
70+
get {
71+
const string __id = "getObject.()Ljava/lang/Object;";
72+
try {
73+
var __rm = _members.InstanceMethods.InvokeVirtualObjectMethod (__id, this, null);
74+
return global::Java.Lang.Object.GetObject<global::Java.Lang.Object> (__rm.Handle, JniHandleOwnership.TransferLocalRef);
75+
} finally {
76+
}
77+
}
78+
}
79+
80+
}
81+
}

0 commit comments

Comments
 (0)