Fsharp: ValueTuple Item1,Item2,.. fields and ITuple interface are not visible or accessible

Created on 16 Sep 2018  路  14Comments  路  Source: dotnet/fsharp

It seems that ValueTuple Item1,Item2,.. fields are not visible or accessible even if the aliased type have them. It's also not possible to cast it to ITuple interface even if implemented by the System.ValueTuple type (ITuple is part of .NET Core 2.0 and .NET Framework 4.7.1 and will be part of .NET Standard 2.1 as of https://github.com/dotnet/standard/pull/800).

Repro steps

open System

type StructTuple2 = (struct(int*int))

[<EntryPoint>]
let main argv =
    let x1: StructTuple2 = Unchecked.defaultof<StructTuple2>
    let xi11 = x1.Item1
    let xi12 = x1.Item2
    //printfn "StructTuple(%d,%d)" xi11 xi12
    let x2: StructTuple2 = struct(1,2)
    let x21 = x2.Item1
    let x22 = x2.Item2
    //printfn "StructTuple(%d,%d)" x21 x22
    0 // return an integer exit code

Expected behavior

Item1,Item2,...fields are accessible

Actual behavior

/tmp/structtupleabbrev/Program.fs(8,16): error FS0039: The field, constructor or member 'Item1' is not defined. [/tmp/structtupleabbrev/structtupleabbrev.fsproj]
/tmp/structtupleabbrev/Program.fs(9,16): error FS0039: The field, constructor or member 'Item2' is not defined. [/tmp/structtupleabbrev/structtupleabbrev.fsproj]

Build FAILED.

/tmp/structtupleabbrev/Program.fs(8,16): error FS0039: The field, constructor or member 'Item1' is not defined. [/tmp/structtupleabbrev/structtupleabbrev.fsproj]
/tmp/structtupleabbrev/Program.fs(9,16): error FS0039: The field, constructor or member 'Item2' is not defined. [/tmp/structtupleabbrev/structtupleabbrev.fsproj]
    0 Warning(s)
    2 Error(s)

However according to the typeof the aliased type have the proper Item* fields:

typeof<StructTuple2>;;
val it : System.Type =
  System.ValueTuple`2[System.Int32,System.Int32]
    {Assembly = mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;
     AssemblyQualifiedName = "System.ValueTuple`2[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
     Attributes = AutoLayout, AnsiClass, Class, Public, Sealed, Serializable, BeforeFieldInit;
     BaseType = System.ValueType;
     ContainsGenericParameters = false;
     CustomAttributes = seq [];
     DeclaredConstructors = [|Void .ctor(Int32, Int32)|];
     DeclaredEvents = [||];
     DeclaredFields = [|System.Int32 Item1; System.Int32 Item2|];

Known workarounds

None.

C# example(using the ITuple interface):

using System.Runtime.CompilerServices;

var item = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

var tuple = item as ITuple;
for(int i = 0; i < tuple.Length; i++)
    Console.Out.WriteLine(tuple[i]); // Prints "1 2 3 4 5 6 7 8 9 10"

Related information

Provide any related information

  • Operating system: Ubuntu 16.04 x86_64
  • .NET Runtime, CoreCLR or Mono Version:
ii  dotnet-host                                                 2.1.4-1                                                     amd64        Microsoft .NET Core Host - 2.1.4
ii  dotnet-hostfxr-2.0.7                                        2.0.7-1                                                     amd64        Microsoft .NET Core Host FX Resolver - 2.0.7 2.0.7
ii  dotnet-hostfxr-2.1                                          2.1.4-1                                                     amd64        Microsoft .NET Core Host FX Resolver - 2.1.4 2.1.4
ii  dotnet-runtime-2.0.7                                        2.0.7-1                                                     amd64        Microsoft .NET Core Runtime - 2.0.7 Microsoft.NETCore.App 2.0.7
ii  dotnet-runtime-2.1                                          2.1.4-1                                                     amd64        Microsoft .NET Core Runtime - 2.1.4 Microsoft.NETCore.App 2.1.4
ii  dotnet-runtime-deps-2.1                                     2.1.4-1                                                     amd64        dotnet-runtime-deps-2.1 2.1.4
ii  dotnet-sdk-2.1                                              2.1.402-1                                                   amd64        Microsoft .NET Core SDK 2.1.402
Area-Compiler Resolution-By Design bug

All 14 comments

As a note, ITuple is not a part of any shipped .NET Standard. It is slated for .NET Standard 2.1.

@cartermp I have updated the related description part as "ITuple is part of .NET Core 2.0 and .NET Framework 4.7.1 and will be part of .NET Standard 2.1 as of dotnet/standard#800".

Labeling this as a bug, I think this is a hole in the struct tuples feature. We should allow the property access and warn on its usage just like reference tuples.

Note accessing Item1 on a reference tuple gives this warning:

  (1,2).Item1;;
  ^^^^^^^^^^^

stdin(1,1): warning FS3220: This method or property is not normally used from F# code, use an explicit tuple pattern for deconstruction instead.

val it : int = 1

It's ok to allow this on struct tuples too but with the same warning.

ALso the F# programming model doesn't let you cast to ITuple

> ((1,2) :?> System.Runtime.CompilerServices.ITuple);;

  ((1,2) :?> System.Runtime.CompilerServices.ITuple);;
  -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stdin(4,2): error FS0016: The type 'int * int' does not have any proper subtypes and cannot be used as the source of a type test or runtime coercion.

Instead you can do this, which works for struct tuples too:

> (box (1,2) :?> System.Runtime.CompilerServices.ITuple);;
val it : System.Runtime.CompilerServices.ITuple = (1, 2)

> (box (struct (1,2)) :?> System.Runtime.CompilerServices.ITuple);;
val it : System.Runtime.CompilerServices.ITuple = (1, 2)

ALso the F# programming model doesn't let you cast to ITuple

Was this intentional or is it a bug? I have a generic argument constrained to ITuple and F# isn't allowing me to pass any tuple values there (cannot do boxing in this case).

This is by design. If you really want you can box-unbox to reveal this functionality on tuples.

F# tuples keep the underlying representation as .NET tuples "at arms length" . For example not all F# tuple types support comparison (e.g. if an tuple element is not comparable), where all .NET tuple types support IComparable under the hood.

This is by design. If you really want you can box-unbox to reveal this functionality on tuples.

@dsyme Sadly this doesn't appear to be possible in all cases. For instance, this type declared in C# appears to be unusable from F#:

public sealed class TupleView<TTuple> where TTuple : System.Runtime.CompilerServices.ITuple
{
    public TupleView (TTuple value)
    {
        // ...
    }
}

I've tried these ways to instantiate it:

TupleView<_>(x, y) // error FS0001: The type ''a * 'b' is not compatible with the type 'System.Runtime.CompilerServices.ITuple'
TupleView<System.Tuple<'x,'y>>(x, y) // error FS0001: The type ''x * 'y' is not compatible with the type 'System.Runtime.CompilerServices.ITuple'
TupleView<_>(System.Tuple.Create(x, y)) // error FS0001: The type ''a * 'b' is not compatible with the type 'System.Runtime.CompilerServices.ITuple'

I've tried these ways to instantiate it:

@chkn You're not boxing. Add box, then cast and you should be fine.

I.e., taking your example code, this compiles:

```f#
let x =
let x, y = 1, 2
TupleView(box(x, y) :?> ITuple)


Also with the redundant `<_>` generics specifier it just works:

```f#
let x = 
    let x, y = 1, 2
    TupleView<_>(box(x, y) :?> ITuple)

Yes, you're right that it compiles, however the type of the generic parameter is then ITuple:

val x : TupleView<ITuple>

In my particular use case, the static type of the generic parameter must be the actual tuple type, so this usage would fail at runtime.

It's also trivial to imagine a hypothetical type that is restricted to struct tuples where this trick wouldn't work:

public class StructTupleView<TTuple> where TTuple : struct, System.Runtime.CompilerServices.ITuple
{
    // ...
}

It's also trivial to imagine a hypothetical type that is restricted to struct tuples where this trick wouldn't work:

I agree this is problematic and you have to resolve to alternative ways to solve the issue.

In my particular use case, the static type of the generic parameter must be the actual tuple type, so this usage would fail at runtime.

@chkn Can you elaborate? I don't see how that's relevant, as the only known information of TTuple is that it inherits from ITuple. Only those methods are available here. The concrete type should not matter (though it is still in there of course, as that's how polymorphism works).

@chkn Can you elaborate?

@abelbraaksma Certainly. This is for my managed SwiftUI binding (https://github.com/chkn/Xamarin.SwiftUI). In order to register a custom view type with Swift, we must know the exact static type of the view body ahead of time. For instance:

public class MyCustomView : View
{
    public VStack<TupleView<(Text, Text)>> Body => ...
}

The above would be a custom view with 2 lines of text. When an instance of MyCustomView is passed to Swift, we use reflection to get the return type of the Body property and pass that metadata to Swift as well. This involves translating the managed tuple type into a Swift tuple type, which involves reflecting over the type arguments of the tuple type (https://github.com/chkn/Xamarin.SwiftUI/blob/master/src/SwiftUI/Swift/Interop/ISwiftValue.cs#L143)

Edit: The above is the place where the conversion happens when we have the value.. there is also the static type translation that occurs when we don't have a value, which is where this issue comes into play: https://github.com/chkn/Xamarin.SwiftUI/blob/master/src/SwiftUI/Swift/Interop/SwiftType.cs#L269

I've had to work around this issue with https://github.com/chkn/Xamarin.SwiftUI/pull/37/files#diff-1515bcbb8bb1d9ba6b7da6a87a2e2714f8110075509ecbf377c4de31c94c564aL14.

@dsyme I think this is definitely a bug because it doesn't seem possible to consume this type from F# without the workaround or reflection.

It's ok to allow this on struct tuples too but with the same warning.

@dsyme, you suggested this as fix, but then perhaps accidentally closed this. It seems that struct tuples are not in line with regular tuples yet.

@chkn, I think your issue is tricky, as if I understand it, you cannot use casting in your scenario. I'd like to help, but I've not yet come to understand your scenario well enough. You may also write out a minimal use case and report it as bug or feature suggestion. Esp if it blocks using certain C# libraries there's a good chance it'll get some attention (or someone jumps in with a good workaround).

Note that ITuple is not available in NetStandard 2.0, and as such, cannot be part of F# until they stop supporting that as minimal version.

Was this page helpful?
0 / 5 - 0 ratings