I noticed strange behavior when making a method call on an interface where there is ambiguity between two inherited interfaces with the same method signature. In C#, the method call is not allowed and is asked to disambiguate by being explicit with either interface. In F#, there is no error, and the behavior is rather odd. This is existing behavior and only noticed it when writing DIM tests.
open System
type IA1 =
abstract M: unit -> unit
type IB1 =
abstract M: unit -> unit
type I1 =
inherit IA1
inherit IB1
type Test () =
interface IA1 with
member __.M () = Console.Write("IA1")
interface IB1 with
member __.M () = Console.Write("IB1")
interface I1
[<EntryPoint>]
let main _ =
let x = Test () :> I1
x.M ()
0
Prints: IB1
In C# (tested with 4 and 8):
using System;
namespace CSharpTest
{
public interface IA1
{
void M();
}
public interface IB1
{
void M();
}
public interface I1 : IA1, IB1
{
}
public class Test : I1
{
void IA1.M()
{
Console.Write("IA1");
}
void IB1.M()
{
Console.Write("IB1");
}
}
class Program
{
static void Main(string[] args)
{
I1 x = new Test();
x.M();
}
}
}
Errors with:
CS0121 The call is ambiguous between the following methods or properties: 'IA1.M()' and 'IB1.M()'
In the F# example, swapping the order of the inherited interfaces:
type I1 =
inherit IA1
inherit IB1
to
type I1 =
inherit IB1
inherit IA1
Will change the print output to: IA1
I can't think of a design where this makes sense. I'd argue this is a bug and dangerous. We should fix it even in F# 4.6 version.
I can't think of a design where this makes sense.
If this is really according to the spec (which I doubt). I'd assume the reasoning is "last definition takes preference / hides previous definition" just as in other parts of the language not possible in C#. (Like you cannot have two 'local variables' in C# with the same name, in F# you can)
I just went through the spec by looking at every mention of "interface" and I couldn't find anything that specifically talked about this.
I would never expect that the order of which you inherit would affect your program. This is something I've been trying to be careful with when getting DIM consumption working, especially since you can have complex diamond inheritance hierarchies.
This is intentional, though I'm open to a warning being added subject to a /langversion switch. The spec certainly needs to be clarified - in particular the phrase "search the hierarchy" in section 14.1.5 "Name Resolution for Members" leaves things unspecified.
In practice I was aware from the outset that there are routines in F# which are sensitive to the order of declaration of interfaces in metadata - name resolution is one example, as is type inference in the presence of multiple type instantiations of the same interface type. A careful search of the codebase may reveal others. Here "the order of declaration of interfaces in metadata" refers to the order of declaration in F# code for F#-defined types, and the order in .NET metadata for .NET types.
We've taken a mixed approach to resolving ambiguities like this:
For modules and values and other static elements accessed via "open", the latest definition wins
For object-oriented elements, the "original" or "first" definition wins (e.g. original members are preferred to extension members)
I'd like to see a new warning being added - I think it's an important improvement but not urgent as we've not yet seen it be something people actually hit in practice.
FSxxx warning: The call is ambiguous between the following methods or properties: 'IA1.M()' and 'IB1.M()', 'IB1.M()' was picked based on language version v4.x.
Thank you @dsyme for the explanation.
I imagine that no one has really noticed, probably because code is out there operating with this behavior and code works without the knowledge of this behavior. But, that's my guess. I don't actually know.
Putting in a warning at this stage is probably sufficient. I agree that it isn't urgent. I really just need to be sure the current behavior will work correctly with DIMs.
Most helpful comment
This is intentional, though I'm open to a warning being added subject to a
/langversionswitch. The spec certainly needs to be clarified - in particular the phrase "search the hierarchy" in section 14.1.5 "Name Resolution for Members" leaves things unspecified.In practice I was aware from the outset that there are routines in F# which are sensitive to the order of declaration of interfaces in metadata - name resolution is one example, as is type inference in the presence of multiple type instantiations of the same interface type. A careful search of the codebase may reveal others. Here "the order of declaration of interfaces in metadata" refers to the order of declaration in F# code for F#-defined types, and the order in .NET metadata for .NET types.
We've taken a mixed approach to resolving ambiguities like this:
For modules and values and other static elements accessed via "open", the latest definition wins
For object-oriented elements, the "original" or "first" definition wins (e.g. original members are preferred to extension members)
I'd like to see a new warning being added - I think it's an important improvement but not urgent as we've not yet seen it be something people actually hit in practice.