views:

143

answers:

3
open System

type Foo() =
     interface Collections.IEnumerable with
         member x.GetEnumerator () = null

type Bar() =
     interface Collections.IEnumerable with
         member x.GetEnumerator () = 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?

+3  A: 

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
kvb
+5  A: 

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 (aka seq<_>)
  • 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
Tomas Petricek
Thanks for the answer, Tomas! actually I've found this bug and submit it to [email protected] a hour ago...
ControlFlow
@ControlFlow: Ah, surely they'll fix it more promptly if they receive two reports in a single hour :-)!
Tomas Petricek
+5  A: 

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
desco
Thank you for explanation!
ControlFlow