@@ -784,9 +784,9 @@ namespace Microsoft.FSharp.Core
784784
785785 let inline anyToString nullStr x =
786786 match box x with
787+ | :? IFormattable as f -> f.ToString( null , CultureInfo.InvariantCulture)
787788 | null -> nullStr
788- | :? System.IFormattable as f -> f.ToString( null , System.Globalization.CultureInfo.InvariantCulture)
789- | obj -> obj.ToString()
789+ | _ -> x.ToString()
790790
791791 let anyToStringShowingNull x = anyToString " null" x
792792
@@ -3749,6 +3749,8 @@ namespace Microsoft.FSharp.Core
37493749 open System.Diagnostics
37503750 open System.Collections .Generic
37513751 open System.Globalization
3752+ open System.Text
3753+ open System.Numerics
37523754 open Microsoft.FSharp .Core
37533755 open Microsoft.FSharp .Core .LanguagePrimitives
37543756 open Microsoft.FSharp .Core .LanguagePrimitives .IntrinsicOperators
@@ -4456,23 +4458,53 @@ namespace Microsoft.FSharp.Core
44564458 when ^ T : ^ T = ( ^T : ( static member op_Explicit : ^T -> nativeint) ( value))
44574459
44584460 [<CompiledName( " ToString" ) >]
4459- let inline string ( value : ^ T) =
4461+ let inline string ( value : ' T) =
44604462 anyToString " " value
4461- // since we have static optimization conditionals for ints below, we need to special-case Enums.
4462- // This way we'll print their symbolic value, as opposed to their integral one (Eg., "A", rather than "1")
4463- when ^ T struct = anyToString " " value
4464- when ^ T : float = ( # " " value : float #) .ToString( " g" , CultureInfo.InvariantCulture)
4465- when ^ T : float32 = ( # " " value : float32 #) .ToString( " g" , CultureInfo.InvariantCulture)
4466- when ^ T : int64 = ( # " " value : int64 #) .ToString( " g" , CultureInfo.InvariantCulture)
4467- when ^ T : int32 = ( # " " value : int32 #) .ToString( " g" , CultureInfo.InvariantCulture)
4468- when ^ T : int16 = ( # " " value : int16 #) .ToString( " g" , CultureInfo.InvariantCulture)
4469- when ^ T : nativeint = ( # " " value : nativeint #) .ToString()
4470- when ^ T : sbyte = ( # " " value : sbyte #) .ToString( " g" , CultureInfo.InvariantCulture)
4471- when ^ T : uint64 = ( # " " value : uint64 #) .ToString( " g" , CultureInfo.InvariantCulture)
4472- when ^ T : uint32 = ( # " " value : uint32 #) .ToString( " g" , CultureInfo.InvariantCulture)
4473- when ^ T : int16 = ( # " " value : int16 #) .ToString( " g" , CultureInfo.InvariantCulture)
4474- when ^ T : unativeint = ( # " " value : unativeint #) .ToString()
4475- when ^ T : byte = ( # " " value : byte #) .ToString( " g" , CultureInfo.InvariantCulture)
4463+ when 'T : string = ( # " " value : string #) // force no-op
4464+
4465+ // Using 'let x = (# ... #) in x.ToString()' leads to better IL, without it, an extra stloc and ldloca.s (get address-of)
4466+ // gets emitted, which are unnecessary. With it, the extra address-of-variable is not created
4467+ when 'T : float = let x = ( # " " value : float #) in x.ToString( null , CultureInfo.InvariantCulture)
4468+ when 'T : float32 = let x = ( # " " value : float32 #) in x.ToString( null , CultureInfo.InvariantCulture)
4469+ when 'T : decimal = let x = ( # " " value : decimal #) in x.ToString( null , CultureInfo.InvariantCulture)
4470+ when 'T : BigInteger = let x = ( # " " value : BigInteger #) in x.ToString( null , CultureInfo.InvariantCulture)
4471+
4472+ // no IFormattable
4473+ when 'T : char = let x = ( # " " value : 'T #) in x.ToString() // use 'T, because char can be an enum
4474+ when 'T : bool = let x = ( # " " value : bool #) in x.ToString()
4475+ when 'T : nativeint = let x = ( # " " value : nativeint #) in x.ToString()
4476+ when 'T : unativeint = let x = ( # " " value : unativeint #) in x.ToString()
4477+
4478+ // Integral types can be enum:
4479+ // It is not possible to distinguish statically between Enum and (any type of) int. For signed types we have
4480+ // to use IFormattable::ToString, as the minus sign can be overridden. Using boxing we'll print their symbolic
4481+ // value if it's an enum, e.g.: 'ConsoleKey.Backspace' gives "Backspace", rather than "8")
4482+ when 'T : sbyte = ( box value :?> IFormattable) .ToString( null , CultureInfo.InvariantCulture)
4483+ when 'T : int16 = ( box value :?> IFormattable) .ToString( null , CultureInfo.InvariantCulture)
4484+ when 'T : int32 = ( box value :?> IFormattable) .ToString( null , CultureInfo.InvariantCulture)
4485+ when 'T : int64 = ( box value :?> IFormattable) .ToString( null , CultureInfo.InvariantCulture)
4486+
4487+ // unsigned integral types have equal behavior with 'T::ToString() vs IFormattable::ToString
4488+ // this allows us to issue the 'constrained' opcode with 'callvirt'
4489+ when 'T : byte = let x = ( # " " value : 'T #) in x.ToString()
4490+ when 'T : uint16 = let x = ( # " " value : 'T #) in x.ToString()
4491+ when 'T : uint32 = let x = ( # " " value : 'T #) in x.ToString()
4492+ when 'T : uint64 = let x = ( # " " value : 'T #) in x.ToString()
4493+
4494+
4495+ // other common mscorlib System struct types
4496+ when 'T : DateTime = let x = ( # " " value : DateTime #) in x.ToString( null , CultureInfo.InvariantCulture)
4497+ when 'T : DateTimeOffset = let x = ( # " " value : DateTimeOffset #) in x.ToString( null , CultureInfo.InvariantCulture)
4498+ when 'T : TimeSpan = let x = ( # " " value : TimeSpan #) in x.ToString( null , CultureInfo.InvariantCulture)
4499+ when 'T : Guid = let x = ( # " " value : Guid #) in x.ToString( null , CultureInfo.InvariantCulture)
4500+ when 'T struct =
4501+ match box value with
4502+ | :? IFormattable as f -> f.ToString( null , CultureInfo.InvariantCulture)
4503+ | _ -> value.ToString()
4504+
4505+ // other commmon mscorlib reference types
4506+ when 'T : StringBuilder = let x = ( # " " value : StringBuilder #) in x.ToString()
4507+ when 'T : IFormattable = let x = ( # " " value : IFormattable #) in x.ToString( null , CultureInfo.InvariantCulture)
44764508
44774509 [<NoDynamicInvocation( isLegacy= true ) >]
44784510 [<CompiledName( " ToChar" ) >]
0 commit comments