@@ -784,9 +784,9 @@ namespace Microsoft.FSharp.Core
784
784
785
785
let inline anyToString nullStr x =
786
786
match box x with
787
+ | :? IFormattable as f -> f.ToString( null , CultureInfo.InvariantCulture)
787
788
| null -> nullStr
788
- | :? System.IFormattable as f -> f.ToString( null , System.Globalization.CultureInfo.InvariantCulture)
789
- | obj -> obj.ToString()
789
+ | _ -> x.ToString()
790
790
791
791
let anyToStringShowingNull x = anyToString " null" x
792
792
@@ -3749,6 +3749,8 @@ namespace Microsoft.FSharp.Core
3749
3749
open System.Diagnostics
3750
3750
open System.Collections .Generic
3751
3751
open System.Globalization
3752
+ open System.Text
3753
+ open System.Numerics
3752
3754
open Microsoft.FSharp .Core
3753
3755
open Microsoft.FSharp .Core .LanguagePrimitives
3754
3756
open Microsoft.FSharp .Core .LanguagePrimitives .IntrinsicOperators
@@ -4456,23 +4458,53 @@ namespace Microsoft.FSharp.Core
4456
4458
when ^ T : ^ T = ( ^T : ( static member op_Explicit : ^T -> nativeint) ( value))
4457
4459
4458
4460
[<CompiledName( " ToString" ) >]
4459
- let inline string ( value : ^ T) =
4461
+ let inline string ( value : ' T) =
4460
4462
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)
4476
4508
4477
4509
[<NoDynamicInvocation( isLegacy= true ) >]
4478
4510
[<CompiledName( " ToChar" ) >]
0 commit comments