Lately I played a bit with the possibilities of automatic upcasting in F#, I know that F# is forcing to be explicit, however I've noticed that upcasting actually kinda exists in some cases.
Below is some code showing cases where the upcasting is working and cases where it is not:
type NameValue(name: string, value: obj) = class end
type GenNameValue<'T>(name: string, value: 'T) = class end
type MoreNameValue<'Name, 'Value>(name: 'Name, value: 'Value) = class end
let takeObjList (source: obj list) = printfn "%A" source
let takeNameValueList (source: NameValue list) = printfn "%A" source
let takeGenNameValueList (source: GenNameValue<obj> list) = printfn "%A" source
let takeMoreNameValueList (source: MoreNameValue<string, obj> list) = printfn "%A" source
let takeAnonymousList (source: {| Name: string; Value: obj |} list) = printfn "%A" source
let takeStructAnonymousList (source: struct {| Name: string; Value: obj |} list) = printfn "%A" source
let takeTupleList (source: (string * obj) list) = printfn "%A" source
let takeStructTupleList (source: struct (string * obj) list) = printfn "%A" source
let takeValueTuples (source: ValueTuple<string, obj> list) = printfn "%A" source
let takeRefTuples (source: Tuple<string, obj> list) = printfn "%A" source
// Implicit upcasting to obj => works
takeObjList [42; "middle"; 16us]
takeNameValueList [NameValue("a", "c"); NameValue("b", 42)]
takeGenNameValueList [GenNameValue<obj>("a", "c"); GenNameValue<obj>("a", 42)]
takeMoreNameValueList [MoreNameValue<string, obj>("a", "c"); MoreNameValue<string, obj>("a", 42)]
takeAnonymousList [{| Name = "a"; Value = "c" |}; {| Name = "b"; Value = 42 |}]
takeStructAnonymousList [{| Name = "a"; Value = "c" |}; {| Name = "b"; Value = 42 |}]
// Implicit upcasting to obj => doesn't work
// [FS0001] This expression was expected to have type 'obj' but here has type 'string'
// [FS0001] This expression was expected to have type 'obj' but here has type 'int'
takeTupleList [("a", "c"); ("b", 42)]
takeStructTupleList [("a", "c"); ("b", 42)]
takeValueTuples [ValueTuple.Create("a", "c"); ValueTuple.Create("a", 42)]
takeRefTuples [Tuple.Create("a", "c"); Tuple.Create("a", 42)]
Additionally, I've also noticed that upcasting tuples individually works but failed instantly when it's a collection:
let build1 a b: (string * obj) list = [a; b]
let build2 a: (string * obj) list = a
// Does work
build1 ("a", "c") ("a", 42) |> ignore
// Does not work
build2 [("a", "c"); ("a", 42)] |> ignore
I would like to know the rationale behind. Imho it looks a bit inconsistent, in some cases you can, in some others you cannot. I'm probably missing something out and I would like to know what.