Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,7 @@ positive.exe
*.bsl.actual
/src/FSharp.DependencyManager.Nuget/StandardError.txt
/src/FSharp.DependencyManager.Nuget/StandardOutput.txt

# Standard output/error files in root directory
StandardOutput.txt
StandardError.txt
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/10.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Shorthand lambda: fix completion for chained calls and analysis for unfinished expression ([PR #18560](https://github.com/dotnet/fsharp/pull/18560))
* Completion: fix previous namespace considered opened [PR #18609](https://github.com/dotnet/fsharp/pull/18609)
* Fix active pattern typechecking regression. ([Issue #18638](https://github.com/dotnet/fsharp/issues/18638), [PR #18642](https://github.com/dotnet/fsharp/pull/18642))
* Fix nullness warnings when casting non-nullable values to `IEquatable<T>` to match C# behavior. ([Issue #18759](https://github.com/dotnet/fsharp/issues/18759))

### Changed
* Use `errorR` instead of `error` in `CheckDeclarations.fs` when possible. ([PR #18645](https://github.com/dotnet/fsharp/pull/18645))
Expand Down
15 changes: 11 additions & 4 deletions src/Compiler/Checking/ConstraintSolver.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1407,11 +1407,17 @@ and SolveTypeEqualsTypeEqns csenv ndeep m2 trace cxsln origl1 origl2 =
ErrorD(ConstraintSolverTupleDiffLengths(csenv.DisplayEnv, csenv.eContextInfo, origl1, origl2, csenv.m, m2))
loop origl1 origl2

and SolveTypeEqualsTypeWithContravarianceEqns (csenv:ConstraintSolverEnv) ndeep m2 trace cxsln origl1 origl2 typars =
and SolveTypeEqualsTypeWithContravarianceEqns (csenv:ConstraintSolverEnv) ndeep m2 trace cxsln origl1 origl2 typars tyconRef =
let isContravariant (t:Typar) =
t.typar_opt_data
|> Option.map (fun d -> d.typar_is_contravariant)
|> Option.defaultValue(false)

// Special case for IEquatable<T>: treat its type parameter as contravariant for nullness purposes
// This matches C# behavior where IEquatable<T> is treated as contravariant for nullness, even though
// it's not formally marked as contravariant in IL.
// See: https://github.com/dotnet/fsharp/issues/18759 and https://github.com/dotnet/roslyn/issues/37187
let isIEquatableContravariantForNullness = tyconRefEq csenv.g tyconRef csenv.g.system_GenericIEquatable_tcref

match origl1, origl2, typars with
| [], [], [] -> CompleteD
Expand All @@ -1425,7 +1431,8 @@ and SolveTypeEqualsTypeWithContravarianceEqns (csenv:ConstraintSolverEnv) ndeep
let h1 =
// For contravariant typars (`<in T> in C#'), if the required type is WithNull, the actual type can have any nullness it wants
// Without this added logic, their nullness would be forced to be equal.
if isContravariant hTp && (nullnessOfTy csenv.g h2).TryEvaluate() = ValueSome NullnessInfo.WithNull then
// Special case: IEquatable<T> is treated as contravariant for nullness purposes to match C# behavior
if (isContravariant hTp || isIEquatableContravariantForNullness) && (nullnessOfTy csenv.g h2).TryEvaluate() = ValueSome NullnessInfo.WithNull then
replaceNullnessOfTy csenv.g.knownWithNull h1
else
h1
Expand Down Expand Up @@ -1534,11 +1541,11 @@ and SolveTypeSubsumesType (csenv: ConstraintSolverEnv) ndeep m2 (trace: Optional
(tyconRefEq g tagc1 g.byrefkind_In_tcr || tyconRefEq g tagc1 g.byrefkind_Out_tcr) ) -> ()
| _ -> return! SolveTypeEqualsType csenv ndeep m2 trace cxsln tag1 tag2
}
| _ -> SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange
| _ -> SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1

| TType_app (tc1, l1, _) , TType_app (tc2, l2, _) when tyconRefEq g tc1 tc2 ->
trackErrors {
do! SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange
do! SolveTypeEqualsTypeWithContravarianceEqns csenv ndeep m2 trace cxsln l1 l2 tc1.TyparsNoRange tc1
do! SolveNullnessSubsumesNullness csenv m2 trace ty1 ty2 (nullnessOfTy g sty1) (nullnessOfTy g sty2)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1540,4 +1540,17 @@ let main _ =
|> compile
//|> verifyIL ["abc"]
|> run
|> verifyOutputContains [|"Test true;,1 true,2 true,3 true,4 true,5 true,6 false,7 true,8 false,9 false,10 false,11 false,12 true"|]
|> verifyOutputContains [|"Test true;,1 true,2 true,3 true,4 true,5 true,6 false,7 true,8 false,9 false,10 false,11 false,12 true"|]

[<Fact>]
let ``No nullness warning when casting non-nullable to IEquatable`` () =
FSharp """module Test

open System

let x = ""
let y = x :> IEquatable<string> // Should not warn about nullness
"""
|> asLibrary
|> typeCheckWithStrictNullness
|> shouldSucceed
Loading