Issue description
The interpretation of the null constraint has changed between F# 8 and 9 in a way that breaks overload resolution happening as a result of using the member constraint, whereby a non-nullable reference type is nonetheless considered nullable. This issue manifests only when an assembly compiled on pre-F# 9 is referenced in F# 9; if the assembly is updated to F# 9 (even without --checknulls), the issue goes away.
Choose one or more from the following categories of impact
Operating System
Windows (Default)
What .NET runtime/SDK kind are you seeing the issue on
.NET SDK (.NET Core, .NET 5+)
.NET Runtime/SDK version
.NET SDK 9.0.201
Reproducible code snippet and actual behavior
The following code (compiled with F# 8) defines a function Test.Method that has the static member M constraint. This member is satisfiable by two methods, one with x : NonNullType<_> and one with x : ^T when ^T : null:
namespace Library
open System
type NonNullType<'T> = class end
type C1 = C1
type C2 = C2
type C1 with
static member inline M(C1, C2, x : NonNullType<_>) =
true
static member inline M(C1, C2, x : ^T when ^T : null) =
false
type C2 with
// Impossible balancing overload
static member inline M(C1, C2, x : ^T when ^T :> Enum and ^T : not struct and ^T : (new : unit -> ^T)) =
()
module Test =
let inline Method x =
((^self1 or ^self2 or ^x) : (static member M: ^self1 * ^self2 * ^x -> _) C1, C2, x)
In a different project (compiled with F# 9) the code above is referenced and used:
open Library
let a : NonNullType<int> = Unchecked.defaultof<_>
let b = Test.Method(a)
printf "%b" b
This results in an error:
error FS0043: A unique overload for method 'M' could not be determined based on type information prior to this program point. A type annotation may be needed.
Known return type: 'a
Known type parameters: < C1 , C2 , NonNullType >
Candidates:
- static member C1.M: C1: C1 * C2: C2 * x: NonNullType<'a> -> bool
- static member C1.M: C1: C1 * C2: C2 * x: ^T -> bool when ^T: null
The second candidate with x : ^T when ^T : null is picked even though NonNullType is definitely non-nullable (which can be attested by observing that let _ : NonNullType<int> = null results in an error).
The only way for this code to be successfully compiled and executed is either both projects being compiled on F# 8 (or older) or both projects being compiled on F# 9. In that case, true is printed.
The same issue can also be demonstrated by removing the x : NonNullType<_> overload. If compiled on F# 8, the code above instead prints false, and errors if compiled on F# 9. It should also be noted that this happens only through member constraints. Normal overload resolution does not seem to be affected (the type is correctly treated as non-nullable).
Attaching the solution (run dotnet run in Test):
FSharpNullOverloadResolutionExample.zip
Possible workarounds
I could not find a solution which did not involve updating the F# version. I did not find any difference in attributes between the two language versions for the assembly, so it must be something in the hidden resource.
Issue description
The interpretation of the
nullconstraint has changed between F# 8 and 9 in a way that breaks overload resolution happening as a result of using the member constraint, whereby a non-nullable reference type is nonetheless considered nullable. This issue manifests only when an assembly compiled on pre-F# 9 is referenced in F# 9; if the assembly is updated to F# 9 (even without--checknulls), the issue goes away.Choose one or more from the following categories of impact
nullconstructs in code not using the checknulls switch.null,not null).Operating System
Windows (Default)
What .NET runtime/SDK kind are you seeing the issue on
.NET SDK (.NET Core, .NET 5+)
.NET Runtime/SDK version
.NET SDK 9.0.201
Reproducible code snippet and actual behavior
The following code (compiled with F# 8) defines a function
Test.Methodthat has thestatic member Mconstraint. This member is satisfiable by two methods, one withx : NonNullType<_>and one withx : ^T when ^T : null:In a different project (compiled with F# 9) the code above is referenced and used:
This results in an error:
The second candidate with
x : ^T when ^T : nullis picked even thoughNonNullTypeis definitely non-nullable (which can be attested by observing thatlet _ : NonNullType<int> = nullresults in an error).The only way for this code to be successfully compiled and executed is either both projects being compiled on F# 8 (or older) or both projects being compiled on F# 9. In that case,
trueis printed.The same issue can also be demonstrated by removing the
x : NonNullType<_>overload. If compiled on F# 8, the code above instead printsfalse, and errors if compiled on F# 9. It should also be noted that this happens only through member constraints. Normal overload resolution does not seem to be affected (the type is correctly treated as non-nullable).Attaching the solution (run
dotnet runinTest):FSharpNullOverloadResolutionExample.zip
Possible workarounds
I could not find a solution which did not involve updating the F# version. I did not find any difference in attributes between the two language versions for the assembly, so it must be something in the hidden resource.