-
Notifications
You must be signed in to change notification settings - Fork 538
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Xamarin.Android.Build.Tasks] Add MSBuild support for DIM. #3533
Conversation
750e599
to
e3e3b86
Compare
What's the benefit/rationale for adding a new Even if it is to make it easier to produce |
} | ||
|
||
[Test] | ||
public void TestOverriddenDefaultInterfaceMethods () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should also be a [Test]
method in which a C# class implements IDefaultMethodsInterface
and both accepts the default implementation and overrides the methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added, which revealed a new issue that required fixing. (dotnet/java-interop#486)
Context: dotnet/android#3533 Consider the following Java interface which contains a default interface method: // Java package com.xamarin.android; public interface DefaultMethodsInterface { default int foo () { return 0; } } This is bound as: // C# Binding public partial interface IDefaultMethodsInterface : IJavaPeerable { static readonly JniPeerMembers _members = new JniPeerMembers ( "java/DefaultMethodsInterface", typeof (IDefaultMethodsInterface)); [Register ("foo", "()V", "…")] virtual unsafe int Foo () { return _members.InstanceMethods.InvokeVirtualInt32Method ("foo.()V", this, null); } } If we "implement" the interface from C# *without* implementing `IDefaultMethodsInterface.Foo()`: // C# class ManagedOverrideDefault : Java.Lang.Object, IDefaultMethodsInterface { } Then invoke the default interface method: var over = new ManagedOverrideDefault (); (over as IDefaultMethodsInterface).Foo (); The attempt to invoke the default implementation of `IDefaultMethodsInterface.Foo()` causes a crash: Java.Lang.NoSuchMethodError : no non-static method "Ljava/lang/Object;.foo()I" The cause of the crash is as follows: `IDefaultMethodsInterface.Foo()` calls `JniPeerMembers.JniInstanceMethods.InvokeVirtualInt32Method()` tries to determine if the method is overridden, and if the method is *not* overridden -- as is the case here -- then we hit the non-virtual invocation path: // src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs var j = Members.GetPeerMembers (self); var n = j.InstanceMethods.GetMethodInfo (encodedMember); return JniEnvironment.InstanceMethods.CallNonvirtualIntMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); `JniInstanceMethods.Members` will be the `IDefaultMethodsInterface._members` field, and `Members.GetPeerMembers()` would return `self.JniPeerMembers`. Because this is a C# class which inherits `Java.Lang.Object`, `Java.Lang.Object._members` is returned. We then attempt to resolve `foo()` from `java.lang.Object`, which *does not exist*, which is why the `NoSuchMethodError` is thrown. The fix is to "intercept" the `Members.GetPeerMembers()` invocation so that `j` refers to `IDefaultMethodsInterface._members`, *not* `Java.Lang.Object._members`. This would allow `n` to refer to `DefaultMethodsInterface.foo()`, which *does* exist, and *can* be invoked via `JniEnvironment.InstanceMethods.CallNonvirtualIntMethod()`. Add the following `JniPeerMembers` constructor overload: partial class JniPeerMembers { public JniPeerMembers (string jniPeerTypeName, Type managedPeerType, bool isInterface); } Update `JniPeerMembers.GetPeerMembers()` so that if `isInterface` is true, we return `this` (the *interface*s `JniPeerMembers` instance). Update `generator` so that `_members` construction within interfaces sets the `isInterface` parameter to true, resulting in: partial interface IDefaultMethodsInterface { static readonly JniPeerMembers _members = new JniPeerMembers ( "java/DefaultMethodsInterface", typeof (IDefaultMethodsInterface), isInterface: true); } This allows `(over as IDefaultMethodsInterface).Foo()` to work without an exception being thrown.
Changes: dotnet/java-interop@29f9707...3fee05c Context: #3533 Fix `generator` output of Java default-interface-methods so that *non*-overrides are dispatched properly to the Java method (and presumably `base` method invocations as well?).
Context: dotnet/android#3533 Consider the following Java interface which contains a default interface method: // Java package com.xamarin.android; public interface DefaultMethodsInterface { default int foo () { return 0; } } This is bound as: // C# Binding public partial interface IDefaultMethodsInterface : IJavaPeerable { static readonly JniPeerMembers _members = new JniPeerMembers ( "java/DefaultMethodsInterface", typeof (IDefaultMethodsInterface)); [Register ("foo", "()V", "…")] virtual unsafe int Foo () { return _members.InstanceMethods.InvokeVirtualInt32Method ("foo.()V", this, null); } } If we "implement" the interface from C# *without* implementing `IDefaultMethodsInterface.Foo()`: // C# class ManagedOverrideDefault : Java.Lang.Object, IDefaultMethodsInterface { } Then invoke the default interface method: var over = new ManagedOverrideDefault (); (over as IDefaultMethodsInterface).Foo (); The attempt to invoke the default implementation of `IDefaultMethodsInterface.Foo()` causes a crash: Java.Lang.NoSuchMethodError : no non-static method "Ljava/lang/Object;.foo()I" The cause of the crash is as follows: `IDefaultMethodsInterface.Foo()` calls `JniPeerMembers.JniInstanceMethods.InvokeVirtualInt32Method()` tries to determine if the method is overridden, and if the method is *not* overridden -- as is the case here -- then we hit the non-virtual invocation path: // src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs var j = Members.GetPeerMembers (self); var n = j.InstanceMethods.GetMethodInfo (encodedMember); return JniEnvironment.InstanceMethods.CallNonvirtualIntMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); `JniInstanceMethods.Members` will be the `IDefaultMethodsInterface._members` field, and `Members.GetPeerMembers()` would return `self.JniPeerMembers`. Because this is a C# class which inherits `Java.Lang.Object`, `Java.Lang.Object._members` is returned. We then attempt to resolve `foo()` from `java.lang.Object`, which *does not exist*, which is why the `NoSuchMethodError` is thrown. The fix is to "intercept" the `Members.GetPeerMembers()` invocation so that `j` refers to `IDefaultMethodsInterface._members`, *not* `Java.Lang.Object._members`. This would allow `n` to refer to `DefaultMethodsInterface.foo()`, which *does* exist, and *can* be invoked via `JniEnvironment.InstanceMethods.CallNonvirtualIntMethod()`. Add the following `JniPeerMembers` constructor overload: partial class JniPeerMembers { public JniPeerMembers (string jniPeerTypeName, Type managedPeerType, bool isInterface); } Update `JniPeerMembers.GetPeerMembers()` so that if `isInterface` is true, we return `this` (the *interface*s `JniPeerMembers` instance). Update `generator` so that `_members` construction within interfaces sets the `isInterface` parameter to true, resulting in: partial interface IDefaultMethodsInterface { static readonly JniPeerMembers _members = new JniPeerMembers ( "java/DefaultMethodsInterface", typeof (IDefaultMethodsInterface), isInterface: true); } This allows `(over as IDefaultMethodsInterface).Foo()` to work without an exception being thrown.
I'm not aware of any benefit to |
Context: dotnet/android#3533 Consider the following Java interface which contains a default interface method: // Java package com.xamarin.android; public interface DefaultMethodsInterface { default int foo () { return 0; } } This is bound as: // C# Binding public partial interface IDefaultMethodsInterface : IJavaPeerable { static readonly JniPeerMembers _members = new JniPeerMembers ( "java/DefaultMethodsInterface", typeof (IDefaultMethodsInterface)); [Register ("foo", "()V", "…")] virtual unsafe int Foo () { return _members.InstanceMethods.InvokeVirtualInt32Method ("foo.()V", this, null); } } If we "implement" the interface from C# *without* implementing `IDefaultMethodsInterface.Foo()`: // C# class ManagedOverrideDefault : Java.Lang.Object, IDefaultMethodsInterface { } Then invoke the default interface method: var over = new ManagedOverrideDefault (); (over as IDefaultMethodsInterface).Foo (); The attempt to invoke the default implementation of `IDefaultMethodsInterface.Foo()` causes a crash: Java.Lang.NoSuchMethodError : no non-static method "Ljava/lang/Object;.foo()I" The cause of the crash is as follows: `IDefaultMethodsInterface.Foo()` calls `JniPeerMembers.JniInstanceMethods.InvokeVirtualInt32Method()` tries to determine if the method is overridden, and if the method is *not* overridden -- as is the case here -- then we hit the non-virtual invocation path: // src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs var j = Members.GetPeerMembers (self); var n = j.InstanceMethods.GetMethodInfo (encodedMember); return JniEnvironment.InstanceMethods.CallNonvirtualIntMethod (self.PeerReference, j.JniPeerType.PeerReference, n, parameters); `JniInstanceMethods.Members` will be the `IDefaultMethodsInterface._members` field, and `Members.GetPeerMembers()` would return `self.JniPeerMembers`. Because this is a C# class which inherits `Java.Lang.Object`, `Java.Lang.Object._members` is returned. We then attempt to resolve `foo()` from `java.lang.Object`, which *does not exist*, which is why the `NoSuchMethodError` is thrown. The fix is to "intercept" the `Members.GetPeerMembers()` invocation so that `j` refers to `IDefaultMethodsInterface._members`, *not* `Java.Lang.Object._members`. This would allow `n` to refer to `DefaultMethodsInterface.foo()`, which *does* exist, and *can* be invoked via `JniEnvironment.InstanceMethods.CallNonvirtualIntMethod()`. Add the following `JniPeerMembers` constructor overload: partial class JniPeerMembers { public JniPeerMembers (string jniPeerTypeName, Type managedPeerType, bool isInterface); } Update `JniPeerMembers.GetPeerMembers()` so that if `isInterface` is true, we return `this` (the *interface*s `JniPeerMembers` instance). Update `generator` so that `_members` construction within interfaces sets the `isInterface` parameter to true, resulting in: partial interface IDefaultMethodsInterface { static readonly JniPeerMembers _members = new JniPeerMembers ( "java/DefaultMethodsInterface", typeof (IDefaultMethodsInterface), isInterface: true); } This allows `(over as IDefaultMethodsInterface).Foo()` to work without an exception being thrown.
2736e5b
to
d76b104
Compare
@@ -494,6 +494,8 @@ Copyright (C) 2012 Xamarin Inc. All rights reserved. | |||
ToolPath="$(MonoAndroidToolsDirectory)" | |||
ToolExe="$(BindingsGeneratorToolExe)" | |||
UseShortFileNames="$(UseShortGeneratorFileNames)" | |||
LangVersion="$(LangVersion)" | |||
EnableInterfaceMembersPreview="$(EnableInterfaceMembersPreview)" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to document this new $(EnableInterfaceMembersPreview)
property yet? https://github.com/xamarin/xamarin-android/blob/master/Documentation/guides/BuildProcess.md
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, I forgot about documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jpobst: please update this section: https://github.com/xamarin/xamarin-android/blob/master/Documentation/guides/BuildProcess.md#binding-project-build-properties
To document the new $(EnableInterfaceMembersPreview)
property.
Additionally, convention is that public properties begin with Android
.
The Preview
suffix is...disturbing? What's the longevity for this property?
Should this instead be a $(_EnableInterfaceMembers)
property? A _
prefix is "private"/"subject to change without notice" designator, so maybe that's what we should use?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intention is to explicitly denote that this field is temporary and not something we are going to support for the rest of time. I am fine with a "private" property if that conveys the same idea. :)
It was force-stopped, for some reason, which was detected as a crash. Ignoring. |
…3533) Context: dotnet/java-interop#25 Use [C#8 Default Interface Members][0] to improve bindings of Java 8 interface constructs, such as [default interface methods][1] and static methods. This allows the following Java type: // Java publc interface Example { void requiredMethod(); default void optionalMethod() { } public static void staticMethod() { } } to be bound as: // C# public partial interface IExample : IJavaPeerable { [Register("requiredMethod", "()V", …)] void RequiredMethod (); [Register("optionalMethod", "()V", …)] public void OptionalMethod() { /* … */ } [Register("staticMethod", "()V", …)] public static void StaticMethod() { /* … */ } } To enable use of C#8 default interface member generation, the following two MSBuild properties must be set: * `$(LangVersion)`, which controls the C# compiler [language syntax version][2], must be set to `preview`. * `$(_EnableInterfaceMembers)` must be set to `True`. For example: <PropertyGroup> <LangVersion>preview</LangVersion> <_EnableInterfaceMembers>True</_EnableInterfaceMembers> </PropertyGroup> In the future, this will key entirely off of the `$(LangVersion)` property and the `$(_EnableInterfaceMembers)` property will be removed. [0]: https://github.com/dotnet/csharplang/blob/f7952cdddf85316a4beec493a0ecc14fcb3241c8/proposals/csharp-8.0/default-interface-methods.md [1]: https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html [2]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/langversion-compiler-option
Adds MSBuild support for DIM to Android Bindings projects.
To enable, add the following to a binding project: