Skip to content

Commit 001a03e

Browse files
authored
[generator] [Register] interface alternatives into a dummy package (#1242)
Fixes: #802 Context: #798 (comment) Context: a3b7456 C# and Java are (famously) different languages, and have (had?) different opinions about e.g. what could be within an `interface` declaration. Consider `java.lang.reflect.Member`: // Java public interface Member { public static final int PUBLIC = 0; public static final int DECLARED = 1; public Class<?> getDeclaringClass(); public String getName(); public int getModifiers(); public boolean isSynthetic(); } How do we bind this in C#? Before C# 8 introduced default interface members, we would bind it as *two* types: the interface binding, and an "interface alternative": // C# 7 [Register ("java/lang/reflect/Member", "", "Java.Lang.Reflect.IMemberInvoker")] public interface IMember : IJavaPeerable { Java.Lang.Class DeclaringClass {get;} bool IsSynthetic {get;} int Modifiers {get;} string Name {get;} } // interface alternative [Register ("java/lang/reflect/Member", DoNotGenerateAcw=true)] public abstract class Member : Java.Lang.Object { internal Member(); /* prevent subclassing */ public const int Declared = 1; public const int Public = 0; } An "interface alternative" is a class with the same name as the Java class, containing all the interface members which couldn't be placed within the interface declaration: static fields, static methods, …. C# 8 allowed `interface`s to contain non-instance members, and with commit a3b7456 this could instead be bound as: // C# 8 [Register ("java/lang/reflect/Member", "", "Java.Lang.Reflect.IMemberInvoker")] public partial interface IMember : Java.Interop.IJavaPeerable { public const int Declared = 1; public const int Public = 0; Java.Lang.Class DeclaringClass {get;} bool IsSynthetic {get;} int Modifiers {get;} string Name {get;} } // No interface alternative However, while this was *possible*, removing the "interface alternative" would be an API break, and thus was retained for existing types (bd7c60a). There is one issue with the interface alternative, as previously declared: the `[Register]` attribute was present, and declared the same Java name as the interface binding: [Register ("java/lang/reflect/Member", "", "Java.Lang.Reflect.IMemberInvoker")] public partial interface IMember {} [Register ("java/lang/reflect/Member", DoNotGenerateAcw=true)] public partial class Member {} which in turn means that often there isn't a 1:1 mapping between Java types and C# types, but instead a 1:2 mapping, an ambiguity. This in turn can creep into [binding projects][0] and cause the generation of invalid code: public partial class JSONArray : global::Com.Alibaba.Fastjson.JSON , global::Java.IO.ISerializable , global::Java.Lang.ICloneable , global::Java.Util.List , global::Java.Util.IRandomAccess {} `Java.Util.List` is the interface alternative for the `java.util.List` interface type, which is a class. The above thus fails to compile with a CS1721 ("Class 'JSONArray' cannot have multiple base classes: 'JSON' and 'List'"). `JSONArray` should instead have implemented the `Java.Util.IList` *interface*, but didn't because of this ambiguity. Remove this ambiguity by instead registering a "fake" class for interface alternatives, using a `mono/internal/` prefix: // interface declaration is unchanged [Register ("java/lang/reflect/Member", "", "Java.Lang.Reflect.IMemberInvoker")] public partial interface IMember {} // interface alternative registers a non-existent type [Register ("mono/internal/java/lang/reflect/Member", DoNotGenerateAcw=true)] public partial class Member {} *Note*: `[Register]` must still be used, because if we remove `[Register]` entirely, then `jcw-gen` will attempt to create a new Java Callable Wrapper for it, as interface alternatives are still `Java.Lang.Object` subclasses. [0]: dotnet/android#9188 (comment)
1 parent d30d554 commit 001a03e

File tree

8 files changed

+17
-14
lines changed

8 files changed

+17
-14
lines changed

tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1-NRT/WriteInterface.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
1+
[Register ("mono/internal/java/code/IMyInterface", DoNotGenerateAcw=true)]
22
public abstract class MyInterface : Java.Lang.Object {
33
internal MyInterface ()
44
{
@@ -19,7 +19,7 @@ public abstract class MyInterface : Java.Lang.Object {
1919

2020
}
2121

22-
[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
22+
[Register ("mono/internal/java/code/IMyInterface", DoNotGenerateAcw=true)]
2323
[global::System.Obsolete (@"Use the 'MyInterface' type. This type will be removed in a future release.", error: true)]
2424
public abstract class MyInterfaceConsts : MyInterface {
2525
private MyInterfaceConsts ()

tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/ObsoleteInterfaceAlternativeClass.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[Register ("com/xamarin/android/Parent", DoNotGenerateAcw=true)]
1+
[Register ("mono/internal/com/xamarin/android/Parent", DoNotGenerateAcw=true)]
22
[global::System.Obsolete (@"Use the 'Com.Xamarin.Android.IParent' type. This class will be removed in a future release.")]
33
public abstract class Parent : Java.Lang.Object {
44
internal Parent ()

tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteConstSugarInterfaceFields.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
1+
[Register ("mono/internal/java/code/IMyInterface", DoNotGenerateAcw=true)]
22
public abstract class MyInterface : Java.Lang.Object {
33
internal MyInterface ()
44
{

tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterface.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
1+
[Register ("mono/internal/java/code/IMyInterface", DoNotGenerateAcw=true)]
22
public abstract class MyInterface : Java.Lang.Object {
33
internal MyInterface ()
44
{
@@ -19,7 +19,7 @@ public abstract class MyInterface : Java.Lang.Object {
1919

2020
}
2121

22-
[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
22+
[Register ("mono/internal/java/code/IMyInterface", DoNotGenerateAcw=true)]
2323
[global::System.Obsolete (@"Use the 'MyInterface' type. This type will be removed in a future release.", error: true)]
2424
public abstract class MyInterfaceConsts : MyInterface {
2525
private MyInterfaceConsts ()

tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteInterfaceFieldAsDimProperty.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[Register ("com/xamarin/android/MyInterface", DoNotGenerateAcw=true)]
1+
[Register ("mono/internal/com/xamarin/android/MyInterface", DoNotGenerateAcw=true)]
22
public abstract class MyInterface : Java.Lang.Object {
33
internal MyInterface ()
44
{
@@ -32,7 +32,7 @@ public abstract class MyInterface : Java.Lang.Object {
3232

3333
}
3434

35-
[Register ("com/xamarin/android/MyInterface", DoNotGenerateAcw=true)]
35+
[Register ("mono/internal/com/xamarin/android/MyInterface", DoNotGenerateAcw=true)]
3636
[global::System.Obsolete (@"Use the 'MyInterface' type. This type will be removed in a future release.", error: true)]
3737
public abstract class MyInterfaceConsts : MyInterface {
3838
private MyInterfaceConsts ()

tests/generator-Tests/Unit-Tests/CodeGeneratorExpectedResults/XAJavaInterop1/WriteStaticInterfaceMethod.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
1+
[Register ("mono/internal/java/code/IMyInterface", DoNotGenerateAcw=true)]
22
public abstract class MyInterface : Java.Lang.Object {
33
internal MyInterface ()
44
{
@@ -19,7 +19,7 @@ public abstract class MyInterface : Java.Lang.Object {
1919

2020
}
2121

22-
[Register ("java/code/IMyInterface", DoNotGenerateAcw=true)]
22+
[Register ("mono/internal/java/code/IMyInterface", DoNotGenerateAcw=true)]
2323
[global::System.Obsolete (@"Use the 'MyInterface' type. This type will be removed in a future release.", error: true)]
2424
public abstract class MyInterfaceConsts : MyInterface {
2525
private MyInterfaceConsts ()

tests/generator-Tests/expected.xaji/TestInterface/Test.ME.ITestInterface.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace Test.ME {
77

8-
[Register ("test/me/TestInterface", DoNotGenerateAcw=true)]
8+
[Register ("mono/internal/test/me/TestInterface", DoNotGenerateAcw=true)]
99
public abstract class TestInterface : Java.Lang.Object {
1010
internal TestInterface ()
1111
{
@@ -31,7 +31,7 @@ internal TestInterface ()
3131

3232
}
3333

34-
[Register ("test/me/TestInterface", DoNotGenerateAcw=true)]
34+
[Register ("mono/internal/test/me/TestInterface", DoNotGenerateAcw=true)]
3535
[global::System.Obsolete (@"Use the 'TestInterface' type. This type will be removed in a future release.", error: true)]
3636
public abstract class TestInterfaceConsts : TestInterface {
3737
private TestInterfaceConsts ()

tools/generator/SourceWriters/InterfaceMemberAlternativeClass.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ public InterfaceMemberAlternativeClass (InterfaceGen iface, CodeGenerationOption
3939

4040
SourceWriterExtensions.AddSupportedOSPlatform (Attributes, iface, opt);
4141

42-
Attributes.Add (new RegisterAttr (iface.RawJniName, noAcw: true, additionalProperties: iface.AdditionalAttributeString ()) { AcwLast = true });
42+
// Place this class in a dummy package that won't conflict the actual interface
43+
Attributes.Add (new RegisterAttr ("mono/internal/" + iface.RawJniName, noAcw: true, additionalProperties: iface.AdditionalAttributeString ()) { AcwLast = true });
4344

4445
if (should_obsolete)
4546
SourceWriterExtensions.AddObsolete (Attributes, $"Use the '{iface.FullName}' type. This class will be removed in a future release.", opt);
@@ -179,7 +180,9 @@ public InterfaceConstsForwardClass (InterfaceGen iface, CodeGenerationOptions op
179180
IsPublic = true;
180181
IsAbstract = true;
181182

182-
Attributes.Add (new RegisterAttr (iface.RawJniName, noAcw: true, additionalProperties: iface.AdditionalAttributeString ()));
183+
// Place this class in a dummy package that won't conflict the actual interface
184+
Attributes.Add (new RegisterAttr ("mono/internal/" + iface.RawJniName, noAcw: true, additionalProperties: iface.AdditionalAttributeString ()));
185+
183186
SourceWriterExtensions.AddObsolete (Attributes, $"Use the '{iface.Name.Substring (1)}' type. This type will be removed in a future release.", opt, isError: true);
184187

185188
Constructors.Add (new ConstructorWriter {

0 commit comments

Comments
 (0)