diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 3e2c18a6ef0..288f14b9de6 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -1,5 +1,6 @@ ### Fixed +* Preserve type abbreviations (`string`, user-defined aliases) in the refined type of bindings introduced after a `| null` pattern in a `match` expression. ([Issue #19646](https://github.com/dotnet/fsharp/issues/19646), [PR #19745](https://github.com/dotnet/fsharp/pull/19745)) * Fix attributes on return type of unparenthesized tuple methods being silently dropped from IL. ([Issue #462](https://github.com/dotnet/fsharp/issues/462), [PR #19714](https://github.com/dotnet/fsharp/pull/19714)) * Fix internal error FS0073 "Undefined or unsolved type variable" in IlxGen when nested inline SRTP functions with multiple overloads leave unsolved typars in the non-witness codegen path. ([Issue #19709](https://github.com/dotnet/fsharp/issues/19709), [PR #19710](https://github.com/dotnet/fsharp/pull/19710)) * Fix NRE when calling virtual Object methods on value types through inline SRTP functions. ([Issue #8098](https://github.com/dotnet/fsharp/issues/8098), [PR #19511](https://github.com/dotnet/fsharp/pull/19511)) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 30d49e776c8..14cd5266ae6 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -10969,11 +10969,18 @@ 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 + // Remove nullness, preserving aliases when possible (#19646): + // `string | null` → `string` (not `System.String`) + // `MyStr | null` → `MyStr` + // Fall back to stripping when nullness is baked into the abbreviation + // RHS, e.g. `type objnull = obj | null` (#18488). + let nonNullOriginal = replaceNullnessOfTy KnownWithoutNull t + match (nullnessOfTy cenv.g nonNullOriginal).TryEvaluate() with + | ValueSome NullnessInfo.WithoutNull -> + nonNullOriginal + | _ -> + let stripped = stripTyEqns cenv.g t + replaceNullnessOfTy KnownWithoutNull stripped let rec isWild (p:Pattern) = match p with | TPat_wild _ -> true diff --git a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs index c36c51b3a15..f5687ffb349 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/Nullness/NullableReferenceTypesTests.fs @@ -2373,3 +2373,25 @@ let main _ = 0 |> compile |> run |> verifyOutputContains [|"-1"|] + +// https://github.com/dotnet/fsharp/issues/19646 +// After `| null -> … | s -> …`, `s` must keep its type alias, not the BCL type. +[] +[] +[] +[] +let ``Issue 19646 - type alias is preserved after null pattern`` + (typeDef: string, paramTypeName: string, expectedSubstring: string) = + FSharp $"""module Test + +{typeDef} + +let test (x: {paramTypeName} | null) : int = + match x with + | null -> 0 + | s -> s + """ + |> asLibrary + |> typeCheckWithStrictNullness + |> shouldFail + |> withDiagnosticMessageMatches expectedSubstring