Cannot pass in voptions to optional arguments marked as [<Struct>]
The release of F# 10 introduces to ability to mark an optional parameter as [<Struct>] to avoid unnecessary heap allocation. However, due to some flaw of the type checker, one now cannot directly pass in a voption to such a parameter.
Repro steps
Suppose there is a method with a [<Struct>] optional argument:
type Foo =
static member Bar([<Struct>] ?x: unit) = x
Now if one pass in a voption, they gets an FS0001:
Foo.Bar(?x=ValueNone)
// error FS0001: This expression was expected to have type
// 'unit option'
// but here has type
// 'unit voption'
Conversely, passing in an option does not work too:
Foo.Bar(?x=None)
// error FS0193: Type mismatch. Type
// “'a option”
// and type
// “unit voption” are incompatible
Expected behavior
The compiler should at least accept the case of voption into [<Struct>] optional parameter. Additionally, the compiler may apply implicit conversion between options and voptions to provide better backward compatiblity.
Actual behavior
It doesn't.
Known workarounds
Avoid explicit optional argument passing, and resort to pattern matching or something to decide whether the argument is to be passed in to the method.
Related information
- .NET SDK: .NET 10
I think it's the issue of this particular syntax, because the following should work:
https://godbolt.org/z/6rev1z7v1:
module Program
type X() =
static member MStruct([<Struct>] ?x) =
match x with
| ValueSome x -> printfn "Some %A" x
| ValueNone -> printfn "None"
static member MRef(?x) =
match x with
| Some x -> printfn "Some %A" x
| None -> printfn "None"
[<EntryPoint>]
let main _ =
X.MStruct(x=ValueSome 1)
X.MStruct(x=ValueNone)
X.MStruct(x=Some 1)
X.MStruct(x=None)
X.MStruct(ValueSome 1)
X.MStruct(ValueNone)
X.MStruct(Some 1)
X.MStruct(None)
X.MStruct(x=1)
X.MStruct()
X.MRef(x=ValueSome 1)
X.MRef(x=ValueNone)
X.MRef(x=Some 1)
X.MRef(x=None)
X.MRef(ValueSome 1)
X.MRef(ValueNone)
X.MRef(Some 1)
X.MRef(None)
X.MRef(x=1)
X.MRef()
0
Yes it is about ?x, and the (not handled) case of if callerArg.IsExplicitOptional then
I think it's the issue of this particular syntax, because the following should work:
https://godbolt.org/z/6rev1z7v1:
module Program type X() = static member MStruct([<Struct>] ?x) = match x with | ValueSome x -> printfn "Some %A" x | ValueNone -> printfn "None" static member MRef(?x) = match x with | Some x -> printfn "Some %A" x | None -> printfn "None" [<EntryPoint>] let main _ = X.MStruct(x=ValueSome 1) X.MStruct(x=ValueNone) X.MStruct(x=Some 1) X.MStruct(x=None) X.MStruct(ValueSome 1) X.MStruct(ValueNone) X.MStruct(Some 1) X.MStruct(None) X.MStruct(x=1) X.MStruct() X.MRef(x=ValueSome 1) X.MRef(x=ValueNone) X.MRef(x=Some 1) X.MRef(x=None) X.MRef(ValueSome 1) X.MRef(ValueNone) X.MRef(Some 1) X.MRef(None) X.MRef(x=1) X.MRef() 0
Unless I misunderstand something, those examples seem misleading to me. They only compile because MStruct and MRef are generic. When passed an option-typed value without ?x=, the actual instantiated signature is _ option option, _ voption voption, etc.
Edit: I guess you mean that those all behave as expected, and that the problem is specifically with ?x, which is correct.
I would expect the struct version to work the same as the reference version:
type T =
static member MRef (?x : int) = ()
static member MStruct ([<Struct>] ?x : int) = ()
T.MRef 3
T.MRef ()
T.MRef (?x=None)
T.MRef (?x=Some 3)
T.MStruct 3
T.MStruct ()
T.MStruct (?x=ValueNone) // Doesn't work, should work.
T.MStruct (?x=ValueSome 3) // Doesn't work, should work.