(Note: Question updated with full reproducible example)
After migrating an F# project from VS 2012 to VS 2015 I receive an error on certain usages of interfaces. In particular it happens where a type implements two generic interfaces. I know this is not allowed in F# directly, but this type comes from C#.
To reproduce the problem:
1. type definition in C#:
Paste this in some class library.
public interface Base { }
public interface IPrime<T> : Base
{
T Value { get; }
}
public interface IFloat : IPrime<double>
{
}
public interface IInt : IFloat, IPrime<int>
{
int Salary { get; }
}
public abstract class Prime<T> : IPrime<T>
{
public T Value { get; protected internal set; }
public static implicit operator T(Prime<T> value)
{
return value.Value;
}
}
public class FFloat : Prime<double>, IFloat
{
public FFloat(double value)
{
this.Value = value;
}
public double Salary { get; set; }
}
public class FInt : Prime<int>, IInt
{
public FInt(int value)
{
this.Value = value;
}
public int Salary { get; set; }
int IPrime<int>.Value { get { return this.Value; } }
double IPrime<double>.Value { get { return this.Value; } }
}
2. Usage of types in F#:
Usage in Visual Studio 2012, working:
open SomeClassLib
[<EntryPoint>]
let main argv =
let i = new FInt(10)
let f = new FFloat(12.0)
let g = fun (itm: SomeClassLib.Base) ->
match itm with
| :? IInt as i -> i.Value
| :? IFloat as i -> i.Value |> int
| _ -> failwith "error"
If you open the same solution in Visual Studio 2015 you get the error
error FS0001: Type mismatch. Expecting a
float -> float
but given afloat -> int
.The type'float'
does not match the type'int'
This is of course easily corrected by a typecast, but then, surprise surprise, it won't load in Visual Studio 2012 anymore (well, there are ways to get it working in both, and in this example it is trivial).
3. Findings
If you hover over the i.Value
you get:
Visual Studio 2012
| :? IInt as i -> i.Value // Value is int
| :? IFloat as i -> i.Value |> int // Value is float
Visual Studio 2015
| :? IInt as i -> i.Value // Value is float
| :? IFloat as i -> i.Value |> int // Value is float
I'm wondering where this difference comes from.
Questions / remarks
I find it strange that the compiler seems to "choose" that one assignment works, and another does not, this invites for inadvertent and sometimes hard-to-get mistakes. And frankly, I'm a little worried that in places were type inference can choose between int and float, it will now favor float, where it was int in the past.
The difference between the 2012 and 2015 seems to be that the former takes the first in encounters when going up the hierarchy, and the latter seems to take the last, but I haven't been able to unambiguously confirm that.
Is this a bug or an improvement of an existing feature? I'm afraid I'll have some redesigning to do to remove the ambiguity, unless someone knows of a simple way to deal with this (it happens in only about 50 or so places, doable to fix by hand, but not so nice)?
Preliminary conclusion
It is clear to me that the original type can be considered ambiguous and possibly poor design, but .NET languages support it and so does MSIL.
I know that F# does not support mixing generic types on the same method or property, which is a language choice I can live with, but its type inference makes a decision that cannot be predicted, which I think is a bug.
Either way, I believe this should be an error, similar to the one you get when you address overloaded members in F#, in which case the error is very clear and lists the options.