diff --git a/docs/release-notes/.FSharp.Core/9.0.200.md b/docs/release-notes/.FSharp.Core/9.0.200.md index 9801210174b..2ea077abe4b 100644 --- a/docs/release-notes/.FSharp.Core/9.0.200.md +++ b/docs/release-notes/.FSharp.Core/9.0.200.md @@ -1,9 +1,12 @@ -### Fixed - -* Fix exception on Post after MailboxProcessor was disposed ([Issue #17849](https://github.com/dotnet/fsharp/issues/17849), [PR #17922](https://github.com/dotnet/fsharp/pull/17922)) - -### Added - -### Changed - -### Breaking Changes +### Fixed + +* Fix exception on Post after MailboxProcessor was disposed ([Issue #17849](https://github.com/dotnet/fsharp/issues/17849), [PR #17922](https://github.com/dotnet/fsharp/pull/17922)) + +### Added + +### Changed +* String function changed to guarantee a non-null string return type ([PR #17809](https://github.com/dotnet/fsharp/pull/17809)) + + +### Breaking Changes + diff --git a/src/FSharp.Core/prim-types.fs b/src/FSharp.Core/prim-types.fs index e1f093e13eb..7c5f12f764d 100644 --- a/src/FSharp.Core/prim-types.fs +++ b/src/FSharp.Core/prim-types.fs @@ -890,11 +890,16 @@ namespace Microsoft.FSharp.Core for m = 0 to len4 - 1 do SetArray4D dst (src1+i) (src2+j) (src3+k) (src4+m) (GetArray4D src i j k m) + let inline defaultIfNull (defaultStr:string) x = + match x with + | null -> defaultStr + | nonNullString -> nonNullString + let inline anyToString nullStr x = match box x with - | :? IFormattable as f -> f.ToString(null, CultureInfo.InvariantCulture) + | :? IFormattable as f -> defaultIfNull nullStr (f.ToString(null, CultureInfo.InvariantCulture)) | null -> nullStr - | _ -> x.ToString() + | _ -> defaultIfNull nullStr (x.ToString()) let anyToStringShowingNull x = anyToString "null" x @@ -5200,8 +5205,8 @@ namespace Microsoft.FSharp.Core when 'T : Guid = let x = (# "" value : Guid #) in x.ToString(null, CultureInfo.InvariantCulture) when 'T struct = match box value with - | :? IFormattable as f -> f.ToString(null, CultureInfo.InvariantCulture) - | _ -> value.ToString() + | :? IFormattable as f -> defaultIfNull "" (f.ToString(null, CultureInfo.InvariantCulture)) + | _ -> defaultIfNull "" (value.ToString()) // other common mscorlib reference types when 'T : StringBuilder = @@ -5210,7 +5215,7 @@ namespace Microsoft.FSharp.Core when 'T : IFormattable = if value = unsafeDefault<'T> then "" - else let x = (# "" value : IFormattable #) in x.ToString(null, CultureInfo.InvariantCulture) + else let x = (# "" value : IFormattable #) in defaultIfNull "" (x.ToString(null, CultureInfo.InvariantCulture)) [] [] diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/OperatorsModule2.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/OperatorsModule2.fs index 57647ead9ed..f2dc93d9ee9 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/OperatorsModule2.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/OperatorsModule2.fs @@ -872,6 +872,15 @@ type OperatorsModule2() = let result = Operators.string 123.456M Assert.AreEqual("123.456", result) + let result = Operators.string { new obj () with override _.ToString () = null } + Assert.AreEqual("", result) + + let result = Operators.string { new obj () with override _.ToString () = Operators.string null } + Assert.AreEqual("", result) + + let result = Operators.string { new IFormattable with override _.ToString (_, _) = null } + Assert.AreEqual("", result) + // Following tests ensure that InvariantCulture is used if type implements IFormattable // safe current culture, then switch culture