When a function returns Some x where x is of type voidptr, the code compiles but throws at runtime.
Repro steps
Simple repro:
```f#
let check () =
let mutable tmp = IntPtr.Zero
let mutable handle = NativeInterop.NativePtr.toVoidPtr &&tmp
Some handle
[
let main argv =
// this throws at runtime
check() |> ignore
A similar approach that also fails (but succeeds with `|> ignore`):
```f#
let check () =
let mutable tmp = IntPtr.Zero
let mutable handle = NativeInterop.NativePtr.toVoidPtr &&tmp
handle
[<EntryPoint>]
let main argv =
// this throws at runtime
printfn "Handle: %A" (check())
Expected behavior
Either a compile error if this is illegal, or a valid result.
Actual behavior
The following error is output to the console:
Unhandled exception. System.TypeLoadException: The generic type 'Microsoft.FSharp.Core.FSharpOption`1' was used with an invalid instantiation in assembly 'FSharp.Core, Version=4.7.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
at Program.main(String[] argv)
Known workarounds
None. Though using your own DU allows this to run with |> ignore, it doesn't allow it to deconstruct. I.e.
This doesn't throw:
```f#
type Handle = Handle of voidptr
let check () =
let mutable tmp = IntPtr.Zero
let mutable handle = NativeInterop.NativePtr.toVoidPtr &&tmp
Handle handle
[
let main argv =
check() |> ignore
But this does:
```f#
let main argv =
// this throws at runtime
match check() with
| Handle x -> printfn "Handle: %A" x
Related information
Seen on any recent version of VS / F#.
Note that I don't know if voidptr is allowed as field. ~Probably not, which would make it understandable that this fails, though a compile-time error would be better.~ EDIT: see below, it works in several scenarios, just not with (v)options.
A possible use-case to allow this (if there're no CLS restrictions) is that returned void pointers are often NULL or valid, and options are a good way to wrap such returns.
Interestingly, this works:
```f#
[
type Handle = Handle of voidptr
let check () =
let mutable tmp = IntPtr.Zero
let mutable handle = NativeInterop.NativePtr.toVoidPtr &&tmp
Handle handle
[
let main argv =
// this succeeds!
let y = match check() with Handle x -> NativeInterop.NativePtr.ofVoidPtr
printfn "%A" y
But this doesn't
```f#
let check () =
let mutable tmp = IntPtr.Zero
let mutable handle = NativeInterop.NativePtr.toVoidPtr &&tmp
Some handle // fails with ValueSome and Some
[<EntryPoint>]
let main argv =
// this throws at runtime
let y = match check() with Some x -> NativeInterop.NativePtr.ofVoidPtr<int> x
printfn "%A" y
Which suggests there's some issue with having voidptr in an option or voption. Though arguably, they should behave the same as any other DU, where this actually works. I can now confirm failing with build-in types:
Some x failsOk x and Error x failsChoice1of2 x failstype Handle<'T> = Handle of 'T it also failsI also noticed that voidptr and nativeptr<_> don't implement ToString() and GetType() (or at least: F# disallows me to access it), which may very well be the reason that the printfn examples fail. EDIT: not so sure, isolated tests succeed.
Since voidptr translates to void* in the CLR type system, and since that type system forbids (afaik) to instantiate a generic type argument with a void*, it stands to reason to disallow this at compile time. The workaround will then always be to convert it to nativeint.
Well, the compiler does warn you:
Uses of this construct may result in the generation of unverifiable .NET IL code. This warning can be disabled using '--nowarn:9' or '#nowarn "9"'.
To be honest I think this is by-design, the warning is there. Perhaps it could be more explicit about "and your program may not run :-))"
This should be an error instead of a warning, since it's clearly a pattern that will never succeed in the first place.
Note that https://github.com/dotnet/fsharp/issues/9957 was closed by design when it was also suggested that we add compiler analysis (as there is no warning or error) similar to byref<'T> where you cannot box it.
"and your program may not run :-))"
:rofl: (let's add the smiley to the error)
This should be an error instead of a warning, since it's clearly a pattern that will never succeed in the first place.
I agree. At least the part where voidptr is used in a generic instantiation. The rest of the warnings should, of course, remain.
Note that C# disallows this properly: it doesn't compile: https://stackoverflow.com/questions/47643007/pointer-as-generic-type-argument
This isn't a feature improvement. This is a bug as all pointer types should not be used as generic instantiations.
@TIHan if this is consiered a but then https://github.com/dotnet/fsharp/issues/9957 should also be considered one, since it's the same root cause: lack of compiler analysis.
@dsyme @TIHan I suggest that both of you get on the same page here, since one of you claim this is a bug and another claims it is by design
ex: nativeptr<byte> compiles down to a System.IntPtr instead of a byte* in IL. Because of this, using nativeptr<byte> as a type argument will be valid at runtime.
voidptr on the other hand, compiles down to void* and not System.IntPtr - which explains why it explodes.
I wasn't aware that we could use nativeptr<_> as type arguments; I've always assumed them to be illegal. But, they are and they work at runtime. This explains why there are not any checks to cover something like voidptr.
I still think this is a bug because we are able to write code that cause type load exceptions when they can easily be prevented.