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
No fields configured for issues without a type.

Projects

Status

Done

Relationships

None yet

Development

No branches or pull requests

Issue actions