Is this an F# compiler bug? #2
open System
type Foo() =
interface Collections.IEnumerable with
member x.GetEnumerator () = null
type Bar() =
interface Collections.IEnumerable with
member x.GetEnu开发者_开发技巧merator () = null
interface Collections.Generic.IEnumerable<int> with
member x.GetEnumerator () = null
let xs, ys = Foo(), Bar()
for x in xs do () // <--
for y in ys do () // fine
The code above produces the following compilation error:
The type 'Foo' is not a type whose values can be enumerated with this syntax, i.e. is not compatible with either seq<_>, IEnumerable<_> or IEnumerable and does not have a GetEnumerator method.
The code looks perfectly legal and the generic version works fine. Is this an F# compiler bug?
I think this is a mismatch between the error message an the specification. As kvb points out, the specification allows for ... in
in only two cases:
- When the type implements generic
IEnumerable<_>
interface (akaseq<_>
) - When the type has a
GetEnumerator
method that returns type with certain properties
If the type implements a non-generic IEnumerable
interface then it doesn't match any of the two conditions. However, if you cast it to IEnumerable
then it will actually be the IEnumerable
type, which matches the second condition. Having a GetEnumerator
member directly in the type (as desco suggests) is also correct, because it also matches the second case.
So, I think that the error message is incorrect, because it says that implementing non-generic IEnumerable
is sufficient, but it is actually not.
However, there seems to be one actual compiler error related to the for
loop. You get a compiler "internal error" when you write the following code (which is not correct, because the inferred generic return type doesn't implement IEnumerator
):
type Foo() =
member x.GetEnumerator () = null
for x in Foo() do () // Internal error here
your sample can be simplified to
type Foo() =
interface Collections.IEnumerable with
member x.GetEnumerator () = null
for x in Foo() do ()
Initially F# compiler tries to assert that source type is implementing IEnumerable <_> After this assertion is failed - it searches accessible GetEnumerator/0 method that returns type with accessible MoveNext()/Current members. Seems that methods from explicit implementation of IEnumerable are are not visible in the Foo type, because code below is valid:
open System
open System.Collections
type Foo() =
member x.GetEnumerator () : IEnumerator = null
for x in Foo() do () // GetEnumerator is accessible in Foo
or
open System
open System.Collections
type Foo() =
interface IEnumerable with
member x.GetEnumerator () : IEnumerator = null
for x in (Foo() :> IEnumerable) do () // IEnumerable has accessible GetEnumerator
I don't think so, but it's not a very helpful error message. See the Sequence Iteration Expressions section of the spec for the details on how for ... in ... do ...
expressions are evaluated. If the type implements IEnumerable<_>
, the pattern just works as expected. Otherwise, the compiler looks for a public (the spec says "accessible") GetEnumerator
method with the right signature and calls that. Because F# interface implementations are explicit, the GetEnumerator
method isn't available without upcasting your Foo
type to IEnumerable
. If you perform the upcast, your code again works as expected:
for x in (xs :> Collections.IEnumerable) do () // fine
精彩评论