Skip to content

Nullness issue - Regression in member constraint overload resolution with null-constrained methods from pre-F# 9-compiled code #18390

@IS4Code

Description

@IS4Code

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

  • Unexpected nullness warning (false positive in nullness checking, code uses --checknulls and langversion:preview).
  • Missing nullness warning in a case which can produce nulls (false negative, code uses --checknulls and langversion:preview).
  • Breaking change related to older null constructs in code not using the checknulls switch.
  • Breaking change related to generic code and explicit type constraints (null, not null).
  • Type inference issue (i.e. code worked without type annotations before, and applying the --checknulls enforces type annotations).
  • C#/F# interop issue related to nullness metadata.
  • Other (none of the categories above apply).

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.

Metadata

Metadata

Assignees

Labels

Area-NullnessIssues related to handling of Nullable Reference TypesBugImpact-Medium(Internal MS Team use only) Describes an issue with moderate impact on existing code.

Type

No type

Projects

Status

Done

Relationships

None yet

Development

No branches or pull requests

Issue actions