C# interface can't be implemented on F#
I am trying to port from C# to F# the jnetinterface branch of the NativeAOT-AndroidHelloJniLib repository, but when attempting to use the basic AndroidToast object, I am encountering 1 design-time error and several runtime errors.
AndroidContext.cs
using Rxmxnx.JNetInterface.Native;
using Rxmxnx.JNetInterface.Types;
using Rxmxnx.JNetInterface.Types.Metadata;
namespace HelloJniLib;
public class AndroidContext : JLocalObject, IClassType<AndroidContext>
{
private static readonly JClassTypeMetadata<AndroidContext> typeMetadata = TypeMetadataBuilder<AndroidContext>
.Create("android/content/Context"u8,
JTypeModifier.Abstract).Build();
static JClassTypeMetadata<AndroidContext> IClassType<AndroidContext>.Metadata => AndroidContext.typeMetadata;
protected AndroidContext(IReferenceType.ClassInitializer initializer) : base(initializer) { }
protected AndroidContext(IReferenceType.GlobalInitializer initializer) : base(initializer) { }
protected AndroidContext(IReferenceType.ObjectInitializer initializer) : base(initializer) { }
static AndroidContext IClassType<AndroidContext>.Create(IReferenceType.ClassInitializer initializer)
=> new(initializer);
static AndroidContext IClassType<AndroidContext>.Create(IReferenceType.ObjectInitializer initializer)
=> new(initializer);
static AndroidContext IClassType<AndroidContext>.Create(IReferenceType.GlobalInitializer initializer)
=> new(initializer);
}
AndroidContext.fs
namespace HelloJniLib
open Rxmxnx.JNetInterface.Native
open Rxmxnx.JNetInterface.Types
type AndroidContext =
inherit JLocalObject
static let typeMetadata = JLocalObject.TypeMetadataBuilder<AndroidContext>.Create("android/content/Context"B).Build()
static member val Metadata = typeMetadata with get
new (initializer: IReferenceType.ClassInitializer) = { inherit JLocalObject(initializer); }
new (initializer: IReferenceType.GlobalInitializer) = { inherit JLocalObject(initializer) }
new (initializer: IReferenceType.ObjectInitializer) = { inherit JLocalObject(initializer) }
interface IClassType<AndroidContext> with
static member get_Metadata() = AndroidContext.Metadata
static member Create(initializer: IReferenceType.ClassInitializer) = new AndroidContext(initializer)
static member Create(initializer: IReferenceType.GlobalInitializer) = new AndroidContext(initializer)
static member Create(initializer: IReferenceType.ObjectInitializer) = new AndroidContext(initializer)
Disign error:
The override 'Create: IReferenceType.ObjectInitializer -> AndroidContext' implements more than one abstract slot, e.g. 'IClassType.Create(initializer: IReferenceType.ObjectInitializer) : AndroidContext' and 'IReferenceType.Create(initializer: IReferenceType.ObjectInitializer) : AndroidContext'
Compilation error:
AndroidContext.fs(17,15): Error FS0366 : No implementation was given for those members:
'IClassType.Create(initializer: IReferenceType.ClassInitializer) : AndroidContext'
'IClassType.Create(initializer: IReferenceType.ObjectInitializer) : AndroidContext'
'IClassType.Create(initializer: IReferenceType.GlobalInitializer) : AndroidContext'
'IReferenceType.Create(initializer: IReferenceType.ObjectInitializer) : AndroidContext'
'IDataType.get_Metadata() : Metadata.JDataTypeMetadata'
Note that all interface members must be implemented and listed under an appropriate 'interface' declaration, e.g. 'interface ... with member ...'.
1>------- Finished building project: AndroidHelloJniFSharp. Succeeded: False. Errors: 1. Warnings: 1
The interface I am trying to implement is a sub-interface that internally implements static methods/properties from its super interfaces with new methods with same signature.
public interface IDataType
{
internal static abstract JTypeKind Kind { get; }
internal static abstract Type? FamilyType { get; }
public static JDataTypeMetadata GetMetadata<TDataType>() where TDataType : IDataType<TDataType>
=> TDataType.Metadata;
public static String GetHash<TDataType>() where TDataType : IDataType<TDataType>
=> IDataType.GetMetadata<TDataType>().Hash;
}
public partial interface IReferenceType : IObject, IDataType, IDisposable
{
static Type IDataType.FamilyType => typeof(JLocalObject);
public new static JReferenceTypeMetadata
GetMetadata<TReference>()
where TReference : JReferenceObject, IReferenceType<TReference>
=> (JReferenceTypeMetadata)IDataType.GetMetadata<TReference>();
}
public interface IClassType : IReferenceType
{
static JTypeKind IDataType.Kind => JTypeKind.Class;
public new static JClassTypeMetadata GetMetadata<TClass>() where TClass : JLocalObject, IClassType<TClass>
=> (JClassTypeMetadata)IDataType.GetMetadata<TClass>();
}
public interface IDataType<out TDataType> : IDataType where TDataType : IDataType<TDataType>
{
internal static virtual JArgumentMetadata Argument { get; } = JArgumentMetadata.Create<TDataType>();
internal static abstract JDataTypeMetadata Metadata { get; }
}
public interface IReferenceType<out TReference> : IReferenceType, IDataType<TReference> where TReference : JReferenceObject, IReferenceType<TReference>
{
internal static IReadOnlySet<Type> TypeInterfaces => TypeInterfaceHelper<TReference>.TypeInterfaces;
protected static abstract TReference Create(ObjectInitializer initializer);
}
public interface IClassType<TClass> : IClassType, IReferenceType<TClass> where TClass : JLocalObject, IClassType<TClass>
{
internal static IReadOnlySet<Type> TypeBaseTypes => BaseTypeHelper<TClass>.TypeBaseTypes;
protected new static abstract JClassTypeMetadata<TClass> Metadata { get; }
static JDataTypeMetadata IDataType<TClass>.Metadata => TClass.Metadata;
protected static abstract TClass Create(ClassInitializer initializer);
protected new static abstract TClass Create(ObjectInitializer initializer);
protected static abstract TClass Create(GlobalInitializer initializer);
static TClass IReferenceType<TClass>.Create(ObjectInitializer initializer) => TClass.Create(initializer);
}
- Operating system : macOS Montery
- .NET 8.0
In F# we usually require explicit interfaces implementations (hence the diagnostic about IClassType). Not sure if it's on purpose or not in statics, need to make a minimal repro and see how it behaves in the instance ones, the hierarchy is quite bloated, hard to see what's going on here without some investigation.
In F# we usually require explicit interfaces implementations (hence the diagnostic about IClassType). Not sure if it's on purpose or not in statics, need to make a minimal repro and see how it behaves in the instance ones, the hierarchy is quite bloated, hard to see what's going on here without some investigation.
It was possible to reduce errors by explicitly implementing the create method from the IReferenceType<> superinterface. As for the Metadata property of the IDataType<> interface, it was impossible for me to do so because it is internal.
The purpose of it being internal is to ensure that it could only be implemented by a own generic type, even though the property itself is not generic. The intention behind the hierarchy is to emulate Java's data types, in this case:
DataType -> ReferenceType -> Class.
There are also these types:
DataType -> PrimitiveType
DataType -> ReferenceType -> Interface
DataType -> ReferenceType -> Enum
DataType -> ReferenceType -> Class -> PrimitiveWrapper
DataType -> ReferenceType -> Class -> Throwable
All interfaces in the hierarchy have static methods that facilitate the use of types that implement them with JNI from .NET.
In F# we usually require explicit interfaces implementations (hence the diagnostic about IClassType). Not sure if it's on purpose or not in statics, need to make a minimal repro and see how it behaves in the instance ones, the hierarchy is quite bloated, hard to see what's going on here without some investigation.
It was possible to reduce errors by explicitly implementing the
createmethod from theIReferenceType<>superinterface. As for theMetadataproperty of theIDataType<>interface, it was impossible for me to do so because it is internal.The purpose of it being internal is to ensure that it could only be implemented by a own generic type, even though the property itself is not generic. The intention behind the hierarchy is to emulate Java's data types, in this case:
DataType -> ReferenceType -> Class.There are also these types:
DataType -> PrimitiveTypeDataType -> ReferenceType -> InterfaceDataType -> ReferenceType -> EnumDataType -> ReferenceType -> Class -> PrimitiveWrapperDataType -> ReferenceType -> Class -> ThrowableAll interfaces in the hierarchy have static methods that facilitate the use of types that implement them with JNI from .NET.
I see, let me try and create a synthetic simple example which would show it with one method and one property and post it here, so we ca figure out whether this needs a language suggestion or a fix.
So, after some testing, it looks like we expect it, since it behaves the same way with instance members:
Here's there repo with minimal repro: https://github.com/vzarytovskii/dotnet-fsharp-issues-17605
This will require a language suggestion, since I assume it was decided long time ago, and not an easy task to resolve cases like - "what implementation do we chose in case of "diamond" interface implementation", etc.
cc @dsyme
@vzarytovskii perhaps discuss this with @dsyme next time you get a chance
We technically can filter out inaccessible slots and check if it's a "new" slot that shadows them, and allow it in this case, but that would be a drastic change of what we allow.
We technically can filter out inaccessible slots and check if it's a "new" slot that shadows them, and allow it in this case, but that would be a drastic change of what we allow.
This was a pain while creating Swift, Kotlin binding libraries in my Xamarin times. I think it makes sense to enable this to make F# more interoperable. I even would offer to help if there is a suggestion approved.
We technically can filter out inaccessible slots and check if it's a "new" slot that shadows them, and allow it in this case, but that would be a drastic change of what we allow.
This was a pain while creating Swift, Kotlin binding libraries in my Xamarin times. I think it makes sense to enable this to make F# more interoperable. I even would offer to help if there is a suggestion approved.
It will be changing something what has been decided long time ago. This will need a suggestion and an explicit approval from @dsyme.
We technically can filter out inaccessible slots and check if it's a "new" slot that shadows them, and allow it in this case, but that would be a drastic change of what we allow.
Note that in this case, not only is the property being hidden by the sub-interface, but theoretically, it is being virtually implemented by the one that is publicly exposed.
protected new static abstract JClassTypeMetadata<TClass> Metadata { get; }
static JDataTypeMetadata IDataType<TClass>.Metadata => TClass.Metadata;
Given Vlad's comments above, this is more of a feature request than a bug. I am going to close this, but feel free to create a language suggestion