diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md index 63ec01c0985..379567effb9 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.0.md @@ -1,5 +1,6 @@ ### Fixed +* Nullness: Fix nullness refinement in match expressions to correctly narrow type to non-null after matching null case. ([Issue #18488](https://github.com/dotnet/fsharp/issues/18488), [PR #18852](https://github.com/dotnet/fsharp/pull/18852)) * Scripts: Fix resolving the dotnet host path when an SDK directory is specified. ([PR #18960](https://github.com/dotnet/fsharp/pull/18960)) * Fix excessive StackGuard thread jumping ([PR #18971](https://github.com/dotnet/fsharp/pull/18971)) * Adjust conservative method-overload duplicate detection rules for nativeptr types ([PR #18911](https://github.com/dotnet/fsharp/pull/18911)) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index e37d244c68b..dbdcec96f65 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -10761,6 +10761,9 @@ and TcMatchClause cenv inputTy (resultTy: OverallTy) env isFirst tpenv synMatchC let inputTypeForNextPatterns= let removeNull t = + // Strip type equations (including abbreviations) and set nullness to non-null. + // For type abbreviations like `type objnull = obj | null`, we need to expand + // the abbreviation and apply non-null to the underlying type. let stripped = stripTyEqns cenv.g t replaceNullnessOfTy KnownWithoutNull stripped let rec isWild (p:Pattern) = diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index de591b580a9..0795680de20 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -1554,3 +1554,72 @@ let y = x :> IEquatable // Should not warn about nullness |> asLibrary |> typeCheckWithStrictNullness |> shouldSucceed + +// Regression for https://github.com/dotnet/fsharp/issues/18488 +// This tests the exact scenario from the issue with obj | null return type +[] +let ``Match null branch should refine variable to non-null - exact issue scenario`` () = + FSharp """module Test + +let bar : obj = + let getEnvironmentVariable : string -> (obj|null) = fun _ -> null + + match "ENVVAR" |> getEnvironmentVariable with + | null -> obj() // return some obj in null case + | x -> x // x should be obj, not obj | null - no warning expected + """ + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldSucceed + +// Regression for https://github.com/dotnet/fsharp/issues/18488 +[] +let ``Match null branch should refine variable to non-null in subsequent branches - type alias`` () = + FSharp """module Test + +// Type alias for nullable obj +type objnull = obj | null + +let getEnvAliasObj (_: string) : objnull = failwith "stub" + +// After matching null case, x should be refined to non-null +let valueAliasObj = + match getEnvAliasObj "ENVVAR" with + | null -> "missing" + | x -> x.GetType().Name // x should be obj, not obj | null - no warning expected + """ + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldSucceed + +// Regression for https://github.com/dotnet/fsharp/issues/18488 +[] +let ``Match null branch should refine variable to non-null in subsequent branches - direct nullable type`` () = + FSharp """module Test + +let getValue () : string | null = failwith "stub" + +// After matching null case, x should be refined to non-null +let result = + match getValue () with + | null -> "missing" + | x -> x.ToUpper() // x should be string, not string | null - no warning expected + """ + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldSucceed + +// Regression for https://github.com/dotnet/fsharp/issues/18488 +[] +let ``Match null branch should refine variable to non-null - Environment.GetEnvironmentVariable`` () = + FSharp """module Test + +// Real-world scenario from issue #18488 +let value = + match System.Environment.GetEnvironmentVariable "ENVVAR" with + | null -> "missing" + | x -> x.ToLower() // x should be string, not string | null - no warning expected + """ + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldSucceed diff --git a/tests/ILVerify/ilverify.ps1 b/tests/ILVerify/ilverify.ps1 index 4770e07f934..f86016b4a68 100644 --- a/tests/ILVerify/ilverify.ps1 +++ b/tests/ILVerify/ilverify.ps1 @@ -27,10 +27,12 @@ Write-Host "Checking whether running on Windows: $IsWindows" Write-Host "Repository path: $repo_path" [string] $script = if ($IsWindows) { Join-Path $repo_path "build.cmd" } else { Join-Path $repo_path "build.sh" } -[string] $additional_arguments = if ($IsWindows) { "-noVisualStudio" } else { "" } +[string] $additional_arguments = if ($IsWindows) { "-noVisualStudio -ci -bootstrap" } else { "" } # Set environment variable to disable UpdateXlf target (not needed for IL verification) $env:UpdateXlfOnBuild = "false" +# Disable PDB conversion for SymStore (not needed for IL verification) +$env:PublishWindowsPdb = "false" # Set configurations to build [string[]] $configurations = @("Debug", "Release")