From db2efcd4c98146ef8917a090db61cf65f7474ee4 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Tue, 13 May 2025 21:53:28 -0400 Subject: [PATCH 1/9] Add tests for `string` on enums & signed integral types --- .../StaticOptimizations.fs | 26 + .../StaticOptimizations/String_Enum.fs | 32 + .../StaticOptimizations/String_Enum.fs.il.bsl | 551 ++++++++++++++++++ .../String_SignedIntegralTypes.fs | 7 + .../String_SignedIntegralTypes.fs.il.bsl | 126 ++++ .../FSharp.Compiler.ComponentTests.fsproj | 1 + .../FSharp.Core/OperatorsModule2.fs | 48 +- 7 files changed, 771 insertions(+), 20 deletions(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/StaticOptimizations.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_SignedIntegralTypes.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_SignedIntegralTypes.fs.il.bsl diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/StaticOptimizations.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/StaticOptimizations.fs new file mode 100644 index 00000000000..d1099c27c20 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/StaticOptimizations.fs @@ -0,0 +1,26 @@ +namespace EmittedIL + +open FSharp.Test +open FSharp.Test.Compiler +open Xunit + +module StaticOptimizations = + let verifyCompilation compilation = + compilation + |> asExe + |> withEmbeddedPdb + |> withEmbedAllSource + |> ignoreWarnings + |> verifyILBaseline + + [] + let String_Enum_fs compilation = + compilation + |> getCompilation + |> verifyCompilation + + [] + let String_SignedIntegralTypes_fs compilation = + compilation + |> getCompilation + |> verifyCompilation diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs new file mode 100644 index 00000000000..ca952d71132 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs @@ -0,0 +1,32 @@ +open System + +module String = + type CharEnum = Char = 'a' + type SByteEnum = SByte = 1y + type Int16Enum = Int16 = 1s + type Int32Enum = Int32 = 1 + type Int64Enum = Int64 = 1L + + type ByteEnum = Byte = 1uy + type UInt16Enum = UInt16 = 1us + type UInt32Enum = UInt32 = 1u + type UInt64Enum = UInt64 = 1UL + + let ``string`` (enum : CharEnum) = string enum + let ``string`` (enum : SByteEnum) = string enum + let ``string`` (enum : Int16Enum) = string enum + let ``string`` (enum : Int32Enum) = string enum + let ``string`` (enum : Int64Enum) = string enum + + let ``string`` (enum : ByteEnum) = string enum + let ``string`` (enum : UInt16Enum) = string enum + let ``string`` (enum : UInt32Enum) = string enum + let ``string`` (enum : UInt64Enum) = string enum + + let ``string<#Enum>`` (enum : #Enum) = string enum + let ``string<'T :> Enum>`` (enum : 'T :> Enum) = string enum + + let ``string<'T when 'T : enum<'U>>`` (enum : 'T when 'T : enum<'U>) = string enum + let ``string<'T when 'T : enum>`` (enum : 'T when 'T : enum) = string enum + + let ``string Unchecked.defaultof`` () = string Unchecked.defaultof diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl new file mode 100644 index 00000000000..47b69b7bf43 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl @@ -0,0 +1,551 @@ + + + + + +.assembly extern runtime { } +.assembly extern FSharp.Core { } +.assembly extern netstandard +{ + .publickeytoken = (CC 7B 13 FF CD 2D DD 51 ) + .ver 2:1:0:0 +} +.assembly assembly +{ + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32, + int32, + int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 ) + + + + + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} +.module assembly.exe + +.imagebase {value} +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 +.corflags 0x00000001 + + + + + +.class public abstract auto ansi sealed assembly + extends [runtime]System.Object +{ + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 ) + .class abstract auto ansi sealed nested public String + extends [runtime]System.Object + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 ) + .class auto ansi serializable sealed nested public CharEnum + extends [runtime]System.Enum + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) + .field public specialname rtspecialname char value__ + .field public static literal valuetype assembly/String/CharEnum Char = char(0x0061) + } + + .class auto ansi serializable sealed nested public SByteEnum + extends [runtime]System.Enum + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) + .field public specialname rtspecialname int8 value__ + .field public static literal valuetype assembly/String/SByteEnum SByte = int8(0x01) + } + + .class auto ansi serializable sealed nested public Int16Enum + extends [runtime]System.Enum + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) + .field public specialname rtspecialname int16 value__ + .field public static literal valuetype assembly/String/Int16Enum Int16 = int16(0x0001) + } + + .class auto ansi serializable sealed nested public Int32Enum + extends [runtime]System.Enum + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) + .field public specialname rtspecialname int32 value__ + .field public static literal valuetype assembly/String/Int32Enum Int32 = int32(0x00000001) + } + + .class auto ansi serializable sealed nested public Int64Enum + extends [runtime]System.Enum + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) + .field public specialname rtspecialname int64 value__ + .field public static literal valuetype assembly/String/Int64Enum Int64 = int64(0x1) + } + + .class auto ansi serializable sealed nested public ByteEnum + extends [runtime]System.Enum + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) + .field public specialname rtspecialname uint8 value__ + .field public static literal valuetype assembly/String/ByteEnum Byte = uint8(0x01) + } + + .class auto ansi serializable sealed nested public UInt16Enum + extends [runtime]System.Enum + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) + .field public specialname rtspecialname uint16 value__ + .field public static literal valuetype assembly/String/UInt16Enum UInt16 = uint16(0x0001) + } + + .class auto ansi serializable sealed nested public UInt32Enum + extends [runtime]System.Enum + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) + .field public specialname rtspecialname uint32 value__ + .field public static literal valuetype assembly/String/UInt32Enum UInt32 = uint32(0x00000001) + } + + .class auto ansi serializable sealed nested public UInt64Enum + extends [runtime]System.Enum + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) + .field public specialname rtspecialname uint64 value__ + .field public static literal valuetype assembly/String/UInt64Enum UInt64 = uint64(0x1) + } + + .method public static string 'string'(valuetype assembly/String/CharEnum 'enum') cil managed + { + + .maxstack 3 + .locals init (valuetype assembly/String/CharEnum V_0) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: constrained. assembly/String/CharEnum + IL_000a: callvirt instance string [netstandard]System.Object::ToString() + IL_000f: ret + } + + .method public static string 'string'(valuetype assembly/String/SByteEnum 'enum') cil managed + { + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: box assembly/String/SByteEnum + IL_0006: unbox.any [runtime]System.IFormattable + IL_000b: ldnull + IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0011: tail. + IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, + class [netstandard]System.IFormatProvider) + IL_0018: ret + } + + .method public static string 'string'(valuetype assembly/String/Int16Enum 'enum') cil managed + { + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: box assembly/String/Int16Enum + IL_0006: unbox.any [runtime]System.IFormattable + IL_000b: ldnull + IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0011: tail. + IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, + class [netstandard]System.IFormatProvider) + IL_0018: ret + } + + .method public static string 'string'(valuetype assembly/String/Int32Enum 'enum') cil managed + { + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: box assembly/String/Int32Enum + IL_0006: unbox.any [runtime]System.IFormattable + IL_000b: ldnull + IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0011: tail. + IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, + class [netstandard]System.IFormatProvider) + IL_0018: ret + } + + .method public static string 'string'(valuetype assembly/String/Int64Enum 'enum') cil managed + { + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: box assembly/String/Int64Enum + IL_0006: unbox.any [runtime]System.IFormattable + IL_000b: ldnull + IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0011: tail. + IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, + class [netstandard]System.IFormatProvider) + IL_0018: ret + } + + .method public static string 'string'(valuetype assembly/String/ByteEnum 'enum') cil managed + { + + .maxstack 3 + .locals init (valuetype assembly/String/ByteEnum V_0) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: constrained. assembly/String/ByteEnum + IL_000a: callvirt instance string [netstandard]System.Object::ToString() + IL_000f: ret + } + + .method public static string 'string'(valuetype assembly/String/UInt16Enum 'enum') cil managed + { + + .maxstack 3 + .locals init (valuetype assembly/String/UInt16Enum V_0) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: constrained. assembly/String/UInt16Enum + IL_000a: callvirt instance string [netstandard]System.Object::ToString() + IL_000f: ret + } + + .method public static string 'string'(valuetype assembly/String/UInt32Enum 'enum') cil managed + { + + .maxstack 3 + .locals init (valuetype assembly/String/UInt32Enum V_0) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: constrained. assembly/String/UInt32Enum + IL_000a: callvirt instance string [netstandard]System.Object::ToString() + IL_000f: ret + } + + .method public static string 'string'(valuetype assembly/String/UInt64Enum 'enum') cil managed + { + + .maxstack 3 + .locals init (valuetype assembly/String/UInt64Enum V_0) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: constrained. assembly/String/UInt64Enum + IL_000a: callvirt instance string [netstandard]System.Object::ToString() + IL_000f: ret + } + + .method public static string 'string<#Enum>'<([runtime]System.Enum) a>(!!a 'enum') cil managed + { + + .maxstack 5 + .locals init (object V_0, + class [runtime]System.IFormattable V_1, + string V_2, + !!a V_3) + IL_0000: ldarg.0 + IL_0001: box !!a + IL_0006: stloc.0 + IL_0007: ldloc.0 + IL_0008: isinst [runtime]System.IFormattable + IL_000d: brtrue.s IL_0014 + + IL_000f: ldloc.0 + IL_0010: brfalse.s IL_0033 + + IL_0012: br.s IL_0039 + + IL_0014: ldloc.0 + IL_0015: unbox.any [runtime]System.IFormattable + IL_001a: stloc.1 + IL_001b: ldloc.1 + IL_001c: ldnull + IL_001d: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0022: callvirt instance string [netstandard]System.IFormattable::ToString(string, + class [netstandard]System.IFormatProvider) + IL_0027: stloc.2 + IL_0028: ldloc.2 + IL_0029: brtrue.s IL_0031 + + IL_002b: ldstr "" + IL_0030: ret + + IL_0031: ldloc.2 + IL_0032: ret + + IL_0033: ldstr "" + IL_0038: ret + + IL_0039: ldarg.0 + IL_003a: stloc.3 + IL_003b: ldloca.s V_3 + IL_003d: constrained. !!a + IL_0043: callvirt instance string [netstandard]System.Object::ToString() + IL_0048: stloc.2 + IL_0049: ldloc.2 + IL_004a: brtrue.s IL_0052 + + IL_004c: ldstr "" + IL_0051: ret + + IL_0052: ldloc.2 + IL_0053: ret + } + + .method public static string 'string<\'T :> Enum>'<([runtime]System.Enum) T>(!!T 'enum') cil managed + { + + .maxstack 5 + .locals init (object V_0, + class [runtime]System.IFormattable V_1, + string V_2, + !!T V_3) + IL_0000: ldarg.0 + IL_0001: box !!T + IL_0006: stloc.0 + IL_0007: ldloc.0 + IL_0008: isinst [runtime]System.IFormattable + IL_000d: brtrue.s IL_0014 + + IL_000f: ldloc.0 + IL_0010: brfalse.s IL_0033 + + IL_0012: br.s IL_0039 + + IL_0014: ldloc.0 + IL_0015: unbox.any [runtime]System.IFormattable + IL_001a: stloc.1 + IL_001b: ldloc.1 + IL_001c: ldnull + IL_001d: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0022: callvirt instance string [netstandard]System.IFormattable::ToString(string, + class [netstandard]System.IFormatProvider) + IL_0027: stloc.2 + IL_0028: ldloc.2 + IL_0029: brtrue.s IL_0031 + + IL_002b: ldstr "" + IL_0030: ret + + IL_0031: ldloc.2 + IL_0032: ret + + IL_0033: ldstr "" + IL_0038: ret + + IL_0039: ldarg.0 + IL_003a: stloc.3 + IL_003b: ldloca.s V_3 + IL_003d: constrained. !!T + IL_0043: callvirt instance string [netstandard]System.Object::ToString() + IL_0048: stloc.2 + IL_0049: ldloc.2 + IL_004a: brtrue.s IL_0052 + + IL_004c: ldstr "" + IL_0051: ret + + IL_0052: ldloc.2 + IL_0053: ret + } + + .method public static string 'string<\'T when \'T : enum<\'U>>'(!!T 'enum') cil managed + { + + .maxstack 5 + .locals init (object V_0, + class [runtime]System.IFormattable V_1, + string V_2, + !!T V_3) + IL_0000: ldarg.0 + IL_0001: box !!T + IL_0006: stloc.0 + IL_0007: ldloc.0 + IL_0008: isinst [runtime]System.IFormattable + IL_000d: brtrue.s IL_0014 + + IL_000f: ldloc.0 + IL_0010: brfalse.s IL_0033 + + IL_0012: br.s IL_0039 + + IL_0014: ldloc.0 + IL_0015: unbox.any [runtime]System.IFormattable + IL_001a: stloc.1 + IL_001b: ldloc.1 + IL_001c: ldnull + IL_001d: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0022: callvirt instance string [netstandard]System.IFormattable::ToString(string, + class [netstandard]System.IFormatProvider) + IL_0027: stloc.2 + IL_0028: ldloc.2 + IL_0029: brtrue.s IL_0031 + + IL_002b: ldstr "" + IL_0030: ret + + IL_0031: ldloc.2 + IL_0032: ret + + IL_0033: ldstr "" + IL_0038: ret + + IL_0039: ldarg.0 + IL_003a: stloc.3 + IL_003b: ldloca.s V_3 + IL_003d: constrained. !!T + IL_0043: callvirt instance string [netstandard]System.Object::ToString() + IL_0048: stloc.2 + IL_0049: ldloc.2 + IL_004a: brtrue.s IL_0052 + + IL_004c: ldstr "" + IL_0051: ret + + IL_0052: ldloc.2 + IL_0053: ret + } + + .method public static string 'string<\'T when \'T : enum>'(!!T 'enum') cil managed + { + + .maxstack 5 + .locals init (object V_0, + class [runtime]System.IFormattable V_1, + string V_2, + !!T V_3) + IL_0000: ldarg.0 + IL_0001: box !!T + IL_0006: stloc.0 + IL_0007: ldloc.0 + IL_0008: isinst [runtime]System.IFormattable + IL_000d: brtrue.s IL_0014 + + IL_000f: ldloc.0 + IL_0010: brfalse.s IL_0033 + + IL_0012: br.s IL_0039 + + IL_0014: ldloc.0 + IL_0015: unbox.any [runtime]System.IFormattable + IL_001a: stloc.1 + IL_001b: ldloc.1 + IL_001c: ldnull + IL_001d: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0022: callvirt instance string [netstandard]System.IFormattable::ToString(string, + class [netstandard]System.IFormatProvider) + IL_0027: stloc.2 + IL_0028: ldloc.2 + IL_0029: brtrue.s IL_0031 + + IL_002b: ldstr "" + IL_0030: ret + + IL_0031: ldloc.2 + IL_0032: ret + + IL_0033: ldstr "" + IL_0038: ret + + IL_0039: ldarg.0 + IL_003a: stloc.3 + IL_003b: ldloca.s V_3 + IL_003d: constrained. !!T + IL_0043: callvirt instance string [netstandard]System.Object::ToString() + IL_0048: stloc.2 + IL_0049: ldloc.2 + IL_004a: brtrue.s IL_0052 + + IL_004c: ldstr "" + IL_0051: ret + + IL_0052: ldloc.2 + IL_0053: ret + } + + .method public static string 'string Unchecked.defaultof'() cil managed + { + + .maxstack 5 + .locals init (class [runtime]System.Enum V_0, + object V_1, + class [runtime]System.IFormattable V_2, + string V_3, + class [runtime]System.Enum V_4) + IL_0000: ldnull + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: box [runtime]System.Enum + IL_0008: stloc.1 + IL_0009: ldloc.1 + IL_000a: isinst [runtime]System.IFormattable + IL_000f: brtrue.s IL_0016 + + IL_0011: ldloc.1 + IL_0012: brfalse.s IL_0035 + + IL_0014: br.s IL_003b + + IL_0016: ldloc.1 + IL_0017: unbox.any [runtime]System.IFormattable + IL_001c: stloc.2 + IL_001d: ldloc.2 + IL_001e: ldnull + IL_001f: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0024: callvirt instance string [netstandard]System.IFormattable::ToString(string, + class [netstandard]System.IFormatProvider) + IL_0029: stloc.3 + IL_002a: ldloc.3 + IL_002b: brtrue.s IL_0033 + + IL_002d: ldstr "" + IL_0032: ret + + IL_0033: ldloc.3 + IL_0034: ret + + IL_0035: ldstr "" + IL_003a: ret + + IL_003b: ldloc.0 + IL_003c: stloc.s V_4 + IL_003e: ldloca.s V_4 + IL_0040: constrained. [runtime]System.Enum + IL_0046: callvirt instance string [netstandard]System.Object::ToString() + IL_004b: stloc.3 + IL_004c: ldloc.3 + IL_004d: brtrue.s IL_0055 + + IL_004f: ldstr "" + IL_0054: ret + + IL_0055: ldloc.3 + IL_0056: ret + } + + } + +} + +.class private abstract auto ansi sealed ''.$assembly + extends [runtime]System.Object +{ + .method public static void main@() cil managed + { + .entrypoint + + .maxstack 8 + IL_0000: ret + } + +} + + + + + + diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_SignedIntegralTypes.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_SignedIntegralTypes.fs new file mode 100644 index 00000000000..984bc4a99f2 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_SignedIntegralTypes.fs @@ -0,0 +1,7 @@ +open System + +module String = + let ``string sbyte`` (value : sbyte) = string value + let ``string int16`` (value : int16) = string value + let ``string int32`` (value : int32) = string value + let ``string int64`` (value : int64) = string value diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_SignedIntegralTypes.fs.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_SignedIntegralTypes.fs.il.bsl new file mode 100644 index 00000000000..68ee34436b8 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_SignedIntegralTypes.fs.il.bsl @@ -0,0 +1,126 @@ + + + + + +.assembly extern runtime { } +.assembly extern FSharp.Core { } +.assembly extern netstandard +{ + .publickeytoken = (CC 7B 13 FF CD 2D DD 51 ) + .ver 2:1:0:0 +} +.assembly assembly +{ + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.FSharpInterfaceDataVersionAttribute::.ctor(int32, + int32, + int32) = ( 01 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 ) + + + + + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} +.module assembly.exe + +.imagebase {value} +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 +.corflags 0x00000001 + + + + + +.class public abstract auto ansi sealed assembly + extends [runtime]System.Object +{ + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 ) + .class abstract auto ansi sealed nested public String + extends [runtime]System.Object + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 ) + .method public static string 'string sbyte'(int8 'value') cil managed + { + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: box [runtime]System.SByte + IL_0006: unbox.any [runtime]System.IFormattable + IL_000b: ldnull + IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0011: tail. + IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, + class [netstandard]System.IFormatProvider) + IL_0018: ret + } + + .method public static string 'string int16'(int16 'value') cil managed + { + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: box [runtime]System.Int16 + IL_0006: unbox.any [runtime]System.IFormattable + IL_000b: ldnull + IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0011: tail. + IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, + class [netstandard]System.IFormatProvider) + IL_0018: ret + } + + .method public static string 'string int32'(int32 'value') cil managed + { + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: box [runtime]System.Int32 + IL_0006: unbox.any [runtime]System.IFormattable + IL_000b: ldnull + IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0011: tail. + IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, + class [netstandard]System.IFormatProvider) + IL_0018: ret + } + + .method public static string 'string int64'(int64 'value') cil managed + { + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: box [runtime]System.Int64 + IL_0006: unbox.any [runtime]System.IFormattable + IL_000b: ldnull + IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0011: tail. + IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, + class [netstandard]System.IFormatProvider) + IL_0018: ret + } + + } + +} + +.class private abstract auto ansi sealed ''.$assembly + extends [runtime]System.Object +{ + .method public static void main@() cil managed + { + .entrypoint + + .maxstack 8 + IL_0000: ret + } + +} + + + + + + diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 36754f92217..221e00be9b8 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -185,6 +185,7 @@ + diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/OperatorsModule2.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/OperatorsModule2.fs index 3e0ea652746..358c155a3a8 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/OperatorsModule2.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/OperatorsModule2.fs @@ -33,6 +33,11 @@ type TestFs0670Error<'T> = // See: https://github.com/dotnet/fsharp/issues/7958 Operators.string x +type CultureWithDifferentNegativeSign () as this = + inherit CultureInfo "" + do this.NumberFormat.NegativeSign <- "🙃" + override _.DisplayName = nameof CultureWithDifferentNegativeSign + type OperatorsModule2() = [] @@ -833,7 +838,6 @@ type OperatorsModule2() = [] member _.string() = - let result = Operators.string null Assert.AreEqual("", result) @@ -884,32 +888,36 @@ type OperatorsModule2() = Assert.AreEqual("", result) // Following tests ensure that InvariantCulture is used if type implements IFormattable - - // safe current culture, then switch culture - let currentCI = Thread.CurrentThread.CurrentCulture - Thread.CurrentThread.CurrentCulture <- CultureInfo.GetCultureInfo("de-DE") - // make sure the culture switch happened, and verify - let wrongResult = 123.456M.ToString() - Assert.AreEqual("123,456", wrongResult) + let runWithCulture culture f = + let currentCulture = Thread.CurrentThread.CurrentCulture + Thread.CurrentThread.CurrentCulture <- culture + try f () finally Thread.CurrentThread.CurrentCulture <- currentCulture - // test that culture has no influence on decimals with `string` - let correctResult = Operators.string 123.456M - Assert.AreEqual("123.456", correctResult) + let numbersAndDates () = + // make sure the culture switch happened, and verify + let wrongResult = 123.456M.ToString() + Assert.AreEqual("123,456", wrongResult) - // make sure that the German culture is indeed selected for DateTime - let dttm = DateTime(2020, 6, 23) - let wrongResult = dttm.ToString() - Assert.AreEqual("23.06.2020 00:00:00", wrongResult) + // test that culture has no influence on decimals with `string` + let correctResult = Operators.string 123.456M + Assert.AreEqual("123.456", correctResult) - // test that culture has no influence on DateTime types when used with `string` - let correctResult = Operators.string dttm - Assert.AreEqual("06/23/2020 00:00:00", correctResult) + // make sure that the German culture is indeed selected for DateTime + let dttm = DateTime(2020, 6, 23) + let wrongResult = dttm.ToString() + Assert.AreEqual("23.06.2020 00:00:00", wrongResult) - // reset the culture - Thread.CurrentThread.CurrentCulture <- currentCI + // test that culture has no influence on DateTime types when used with `string` + let correctResult = Operators.string dttm + Assert.AreEqual("06/23/2020 00:00:00", correctResult) + let enums () = + Assert.AreEqual("Wednesday", Operators.string DayOfWeek.Wednesday) + Assert.AreEqual($"%s{Thread.CurrentThread.CurrentCulture.NumberFormat.NegativeSign}1", Operators.string (enum -1)) + numbersAndDates |> runWithCulture (CultureInfo.GetCultureInfo "de-DE") + enums |> runWithCulture (CultureWithDifferentNegativeSign ()) [] member _.``string: don't raise FS0670 anymore``() = From f696d0e48de56abffc07bfb1890cf4bef327825e Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Tue, 13 May 2025 21:56:24 -0400 Subject: [PATCH 2/9] Add special support for `when 'T : Enum` This special-casing allows us to update FSharp.Core to avoid boxing when caling the `string` function on enums and signed integral types going forward while still allowing the updated version of FSharp.Core to be fully compatible with older compilers. Adding support for some form of constraint in library-only static optimizations instead would have been problematic for multiple reasons. Supporting something like `when 'T : enum<'U>` would have required additional modifications to the compiler and would not have been consumable by older compilers. It would also introduce a new type variable. While something like `when 'T : 'T & #Enum` is already syntactically valid, it would add that constraint to the entire `string` function without further modification to the typechecker. It would also not be consumable by older compilers. I think adding a special case for enums is justifiable since (1) enums are a special kind of type to begin with, and (2) static optimization constraints are only allowed in FSharp.Core, so the change to the language itself is quite small. --- src/Compiler/TypedTree/TcGlobals.fs | 2 ++ src/Compiler/TypedTree/TcGlobals.fsi | 2 ++ src/Compiler/TypedTree/TypedTreeOps.fs | 16 ++++++++-- src/FSharp.Core/prim-types.fs | 43 +++++++++++++++++++++++--- src/FSharp.Core/prim-types.fsi | 12 +++++++ 5 files changed, 68 insertions(+), 7 deletions(-) diff --git a/src/Compiler/TypedTree/TcGlobals.fs b/src/Compiler/TypedTree/TcGlobals.fs index 93dd8905553..8f82eebc078 100644 --- a/src/Compiler/TypedTree/TcGlobals.fs +++ b/src/Compiler/TypedTree/TcGlobals.fs @@ -1278,6 +1278,8 @@ type TcGlobals( member val ArrayCollector_tcr = mk_MFCompilerServices_tcref fslibCcu "ArrayCollector`1" + member val SupportsWhenTEnum_tcr = mk_MFCompilerServices_tcref fslibCcu "SupportsWhenTEnum" + member _.TryEmbedILType(tref: ILTypeRef, mkEmbeddableType: unit -> ILTypeDef) = if tref.Scope = ILScopeRef.Local && not(embeddedILTypeDefs.ContainsKey(tref.Name)) then embeddedILTypeDefs.TryAdd(tref.Name, mkEmbeddableType()) |> ignore diff --git a/src/Compiler/TypedTree/TcGlobals.fsi b/src/Compiler/TypedTree/TcGlobals.fsi index b8c3610ef91..f7a3ca11e0c 100644 --- a/src/Compiler/TypedTree/TcGlobals.fsi +++ b/src/Compiler/TypedTree/TcGlobals.fsi @@ -276,6 +276,8 @@ type internal TcGlobals = member ListCollector_tcr: FSharp.Compiler.TypedTree.EntityRef + member SupportsWhenTEnum_tcr: FSharp.Compiler.TypedTree.EntityRef + member MatchFailureException_tcr: FSharp.Compiler.TypedTree.EntityRef member ResumableCode_tcr: FSharp.Compiler.TypedTree.EntityRef diff --git a/src/Compiler/TypedTree/TypedTreeOps.fs b/src/Compiler/TypedTree/TypedTreeOps.fs index a1258657e78..b14dbd753b1 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.fs @@ -5801,11 +5801,23 @@ type StaticOptimizationAnswer = // ^T : ^T --> used in (+), (-) etc. to guard witness-invoking implementations added in F# 5 // 'T : 'T --> used in FastGenericEqualityComparer, FastGenericComparer to guard struct/tuple implementations // +// For performance and compatibility reasons, 'T when 'T is an enum is handled with its own special hack. +// Unlike for other 'T : tycon constraints, 'T can be any enum; it need not (and indeed must not) be identical to System.Enum itself. +// 'T : Enum +// +// In order to add this hack in a backwards-compatible way, we must hide this capability behind a marker type +// which we use solely as an indicator of whether the compiler understands `when 'T : Enum`. +// 'T : SupportsWhenTEnum +// // canDecideTyparEqn is set to true in IlxGen when the witness-invoking implementation can be used. let decideStaticOptimizationConstraint g c canDecideTyparEqn = match c with | TTyconEqualsTycon (a, b) when canDecideTyparEqn && typeEquiv g a b && isTyparTy g a -> - StaticOptimizationAnswer.Yes + StaticOptimizationAnswer.Yes + | TTyconEqualsTycon (_, b) when tryTcrefOfAppTy g b |> ValueOption.exists (tyconRefEq g g.SupportsWhenTEnum_tcr) -> + StaticOptimizationAnswer.Yes + | TTyconEqualsTycon (a, b) when isEnumTy g a && not (typeEquiv g a g.system_Enum_ty) && typeEquiv g b g.system_Enum_ty -> + StaticOptimizationAnswer.Yes | TTyconEqualsTycon (a, b) -> // Both types must be nominal for a definite result let rec checkTypes a b = @@ -5815,7 +5827,7 @@ let decideStaticOptimizationConstraint g c canDecideTyparEqn = let b = normalizeEnumTy g (stripTyEqnsAndMeasureEqns g b) match b with | AppTy g (tcref2, _) -> - if tyconRefEq g tcref1 tcref2 then StaticOptimizationAnswer.Yes else StaticOptimizationAnswer.No + if tyconRefEq g tcref1 tcref2 && not (typeEquiv g a g.system_Enum_ty) then StaticOptimizationAnswer.Yes else StaticOptimizationAnswer.No | RefTupleTy g _ | FunTy g _ -> StaticOptimizationAnswer.No | _ -> StaticOptimizationAnswer.Unknown diff --git a/src/FSharp.Core/prim-types.fs b/src/FSharp.Core/prim-types.fs index d9a7e41bf55..355c97d1ea8 100644 --- a/src/FSharp.Core/prim-types.fs +++ b/src/FSharp.Core/prim-types.fs @@ -391,6 +391,18 @@ namespace Microsoft.FSharp.Core type TailCallAttribute() = inherit System.Attribute() +namespace Microsoft.FSharp.Core.CompilerServices + + open Microsoft.FSharp.Core + + /// + /// A marker type that only compilers that support the when 'T : Enum + /// library-only static optimization constraint will recognize. + /// + [] + [] + type SupportsWhenTEnum = class end + #if !NET5_0_OR_GREATER namespace System.Diagnostics.CodeAnalysis @@ -5149,11 +5161,10 @@ namespace Microsoft.FSharp.Core when ^T : decimal = (# "conv.i" (int64 (# "" value : decimal #)) : unativeint #) when ^T : ^T = (^T : (static member op_Explicit: ^T -> nativeint) (value)) - [] - let inline string (value: 'T) = - anyToString "" value + let inline defaultString (value : 'T) = + anyToString "" value - when 'T : string = + when 'T : string = if value = unsafeDefault<'T> then "" else (# "" value : string #) // force no-op @@ -5186,7 +5197,6 @@ namespace Microsoft.FSharp.Core when 'T : uint32 = let x = (# "" value : 'T #) in x.ToString() when 'T : uint64 = let x = (# "" value : 'T #) in x.ToString() - // other common mscorlib System struct types when 'T : DateTime = let x = (# "" value : DateTime #) in x.ToString(null, CultureInfo.InvariantCulture) when 'T : DateTimeOffset = let x = (# "" value : DateTimeOffset #) in x.ToString(null, CultureInfo.InvariantCulture) @@ -5206,6 +5216,29 @@ namespace Microsoft.FSharp.Core if value = unsafeDefault<'T> then "" else let x = (# "" value : IFormattable #) in defaultIfNull "" (x.ToString(null, CultureInfo.InvariantCulture)) + [] + let inline string (value: 'T) = + defaultString value + + // Only compilers that understand `when 'T : SupportsWhenTEnum` will understand `when 'T : Enum`. + when 'T : CompilerServices.SupportsWhenTEnum = + ( + let inline string (value : 'T) = + defaultString value + + // Special handling for enums whose underlying type is a signed integral type. + // The runtime value may be outside the defined members of the enum, and the negative sign may be overridden. + when 'T : Enum = let x = (# "" value : 'T #) in x.ToString() // Use 'T to constrain the call to the specific enum type. + + // For compilers that understand `when 'T : Enum`, we can safely make a constrained call on the integral type itself here. + when 'T : sbyte = let x = (# "" value : sbyte #) in x.ToString(null, CultureInfo.InvariantCulture) + when 'T : int16 = let x = (# "" value : int16 #) in x.ToString(null, CultureInfo.InvariantCulture) + when 'T : int32 = let x = (# "" value : int32 #) in x.ToString(null, CultureInfo.InvariantCulture) + when 'T : int64 = let x = (# "" value : int64 #) in x.ToString(null, CultureInfo.InvariantCulture) + + string value + ) + [] [] let inline char (value: ^T) = diff --git a/src/FSharp.Core/prim-types.fsi b/src/FSharp.Core/prim-types.fsi index 7c2f171a600..d340ccf036d 100644 --- a/src/FSharp.Core/prim-types.fsi +++ b/src/FSharp.Core/prim-types.fsi @@ -994,6 +994,18 @@ namespace Microsoft.FSharp.Core inherit System.Attribute new : unit -> TailCallAttribute +namespace Microsoft.FSharp.Core.CompilerServices + + open Microsoft.FSharp.Core + + /// + /// A marker type that only compilers that support the when 'T : Enum + /// library-only static optimization constraint will recognize. + /// + [] + [] + type SupportsWhenTEnum = class end + namespace System.Diagnostics.CodeAnalysis open System From d0870cef5f403ed2d7dcb188aa2e24314183e83e Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Tue, 13 May 2025 22:15:40 -0400 Subject: [PATCH 3/9] Update with new IL --- .../Nullness/ReferenceDU.fs.il.net472.bsl | 40 ++++++------ .../Nullness/ReferenceDU.fs.il.netcore.bsl | 40 ++++++------ .../StaticOptimizations/String_Enum.fs.il.bsl | 64 ++++++++----------- .../String_SignedIntegralTypes.fs.il.bsl | 60 +++++++---------- 4 files changed, 92 insertions(+), 112 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/ReferenceDU.fs.il.net472.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/ReferenceDU.fs.il.net472.bsl index fbb185005e8..63458b70509 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/ReferenceDU.fs.il.net472.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/ReferenceDU.fs.il.net472.bsl @@ -662,8 +662,9 @@ class MyTestModule/MyDu/JustInt V_2, int32 V_3, int32 V_4, - class MyTestModule/MyDu/MaybeString V_5, - string V_6) + int32 V_5, + class MyTestModule/MyDu/MaybeString V_6, + string V_7) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloc.0 @@ -677,7 +678,7 @@ IL_000f: ldloc.1 IL_0010: isinst MyTestModule/MyDu/MaybeString - IL_0015: brtrue.s IL_0048 + IL_0015: brtrue.s IL_0040 IL_0017: br.s IL_001b @@ -696,23 +697,22 @@ IL_002b: ldloc.3 IL_002c: stloc.s V_4 IL_002e: ldloc.s V_4 - IL_0030: box [runtime]System.Int32 - IL_0035: unbox.any [runtime]System.IFormattable - IL_003a: ldnull - IL_003b: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() - IL_0040: tail. - IL_0042: callvirt instance string [netstandard]System.IFormattable::ToString(string, - class [netstandard]System.IFormatProvider) - IL_0047: ret - - IL_0048: ldloc.0 - IL_0049: castclass MyTestModule/MyDu/MaybeString - IL_004e: stloc.s V_5 - IL_0050: ldloc.s V_5 - IL_0052: ldfld string MyTestModule/MyDu/MaybeString::_nullableString - IL_0057: stloc.s V_6 - IL_0059: ldloc.s V_6 - IL_005b: ret + IL_0030: stloc.s V_5 + IL_0032: ldloca.s V_5 + IL_0034: ldnull + IL_0035: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_003a: call instance string [netstandard]System.Int32::ToString(string, + class [netstandard]System.IFormatProvider) + IL_003f: ret + + IL_0040: ldloc.0 + IL_0041: castclass MyTestModule/MyDu/MaybeString + IL_0046: stloc.s V_6 + IL_0048: ldloc.s V_6 + IL_004a: ldfld string MyTestModule/MyDu/MaybeString::_nullableString + IL_004f: stloc.s V_7 + IL_0051: ldloc.s V_7 + IL_0053: ret } } diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/ReferenceDU.fs.il.netcore.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/ReferenceDU.fs.il.netcore.bsl index 44ee6d4e2c8..423360aff64 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/ReferenceDU.fs.il.netcore.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Nullness/ReferenceDU.fs.il.netcore.bsl @@ -662,8 +662,9 @@ class MyTestModule/MyDu/JustInt V_2, int32 V_3, int32 V_4, - class MyTestModule/MyDu/MaybeString V_5, - string V_6) + int32 V_5, + class MyTestModule/MyDu/MaybeString V_6, + string V_7) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloc.0 @@ -677,7 +678,7 @@ IL_000f: ldloc.1 IL_0010: isinst MyTestModule/MyDu/MaybeString - IL_0015: brtrue.s IL_0048 + IL_0015: brtrue.s IL_0040 IL_0017: br.s IL_001b @@ -696,23 +697,22 @@ IL_002b: ldloc.3 IL_002c: stloc.s V_4 IL_002e: ldloc.s V_4 - IL_0030: box [runtime]System.Int32 - IL_0035: unbox.any [runtime]System.IFormattable - IL_003a: ldnull - IL_003b: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() - IL_0040: tail. - IL_0042: callvirt instance string [netstandard]System.IFormattable::ToString(string, - class [netstandard]System.IFormatProvider) - IL_0047: ret - - IL_0048: ldloc.0 - IL_0049: castclass MyTestModule/MyDu/MaybeString - IL_004e: stloc.s V_5 - IL_0050: ldloc.s V_5 - IL_0052: ldfld string MyTestModule/MyDu/MaybeString::_nullableString - IL_0057: stloc.s V_6 - IL_0059: ldloc.s V_6 - IL_005b: ret + IL_0030: stloc.s V_5 + IL_0032: ldloca.s V_5 + IL_0034: ldnull + IL_0035: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_003a: call instance string [netstandard]System.Int32::ToString(string, + class [netstandard]System.IFormatProvider) + IL_003f: ret + + IL_0040: ldloc.0 + IL_0041: castclass MyTestModule/MyDu/MaybeString + IL_0046: stloc.s V_6 + IL_0048: ldloc.s V_6 + IL_004a: ldfld string MyTestModule/MyDu/MaybeString::_nullableString + IL_004f: stloc.s V_7 + IL_0051: ldloc.s V_7 + IL_0053: ret } } diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl index 47b69b7bf43..740505ce6a7 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_Enum.fs.il.bsl @@ -130,61 +130,53 @@ .method public static string 'string'(valuetype assembly/String/SByteEnum 'enum') cil managed { - .maxstack 8 + .maxstack 3 + .locals init (valuetype assembly/String/SByteEnum V_0) IL_0000: ldarg.0 - IL_0001: box assembly/String/SByteEnum - IL_0006: unbox.any [runtime]System.IFormattable - IL_000b: ldnull - IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() - IL_0011: tail. - IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, - class [netstandard]System.IFormatProvider) - IL_0018: ret + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: constrained. assembly/String/SByteEnum + IL_000a: callvirt instance string [netstandard]System.Object::ToString() + IL_000f: ret } .method public static string 'string'(valuetype assembly/String/Int16Enum 'enum') cil managed { - .maxstack 8 + .maxstack 3 + .locals init (valuetype assembly/String/Int16Enum V_0) IL_0000: ldarg.0 - IL_0001: box assembly/String/Int16Enum - IL_0006: unbox.any [runtime]System.IFormattable - IL_000b: ldnull - IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() - IL_0011: tail. - IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, - class [netstandard]System.IFormatProvider) - IL_0018: ret + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: constrained. assembly/String/Int16Enum + IL_000a: callvirt instance string [netstandard]System.Object::ToString() + IL_000f: ret } .method public static string 'string'(valuetype assembly/String/Int32Enum 'enum') cil managed { - .maxstack 8 + .maxstack 3 + .locals init (valuetype assembly/String/Int32Enum V_0) IL_0000: ldarg.0 - IL_0001: box assembly/String/Int32Enum - IL_0006: unbox.any [runtime]System.IFormattable - IL_000b: ldnull - IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() - IL_0011: tail. - IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, - class [netstandard]System.IFormatProvider) - IL_0018: ret + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: constrained. assembly/String/Int32Enum + IL_000a: callvirt instance string [netstandard]System.Object::ToString() + IL_000f: ret } .method public static string 'string'(valuetype assembly/String/Int64Enum 'enum') cil managed { - .maxstack 8 + .maxstack 3 + .locals init (valuetype assembly/String/Int64Enum V_0) IL_0000: ldarg.0 - IL_0001: box assembly/String/Int64Enum - IL_0006: unbox.any [runtime]System.IFormattable - IL_000b: ldnull - IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() - IL_0011: tail. - IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, - class [netstandard]System.IFormatProvider) - IL_0018: ret + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: constrained. assembly/String/Int64Enum + IL_000a: callvirt instance string [netstandard]System.Object::ToString() + IL_000f: ret } .method public static string 'string'(valuetype assembly/String/ByteEnum 'enum') cil managed diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_SignedIntegralTypes.fs.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_SignedIntegralTypes.fs.il.bsl index 68ee34436b8..c1470054a4d 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_SignedIntegralTypes.fs.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StaticOptimizations/String_SignedIntegralTypes.fs.il.bsl @@ -46,60 +46,48 @@ { .maxstack 8 - IL_0000: ldarg.0 - IL_0001: box [runtime]System.SByte - IL_0006: unbox.any [runtime]System.IFormattable - IL_000b: ldnull - IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() - IL_0011: tail. - IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, - class [netstandard]System.IFormatProvider) - IL_0018: ret + IL_0000: ldarga.s 'value' + IL_0002: ldnull + IL_0003: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0008: call instance string [netstandard]System.SByte::ToString(string, + class [netstandard]System.IFormatProvider) + IL_000d: ret } .method public static string 'string int16'(int16 'value') cil managed { .maxstack 8 - IL_0000: ldarg.0 - IL_0001: box [runtime]System.Int16 - IL_0006: unbox.any [runtime]System.IFormattable - IL_000b: ldnull - IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() - IL_0011: tail. - IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, - class [netstandard]System.IFormatProvider) - IL_0018: ret + IL_0000: ldarga.s 'value' + IL_0002: ldnull + IL_0003: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0008: call instance string [netstandard]System.Int16::ToString(string, + class [netstandard]System.IFormatProvider) + IL_000d: ret } .method public static string 'string int32'(int32 'value') cil managed { .maxstack 8 - IL_0000: ldarg.0 - IL_0001: box [runtime]System.Int32 - IL_0006: unbox.any [runtime]System.IFormattable - IL_000b: ldnull - IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() - IL_0011: tail. - IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, - class [netstandard]System.IFormatProvider) - IL_0018: ret + IL_0000: ldarga.s 'value' + IL_0002: ldnull + IL_0003: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0008: call instance string [netstandard]System.Int32::ToString(string, + class [netstandard]System.IFormatProvider) + IL_000d: ret } .method public static string 'string int64'(int64 'value') cil managed { .maxstack 8 - IL_0000: ldarg.0 - IL_0001: box [runtime]System.Int64 - IL_0006: unbox.any [runtime]System.IFormattable - IL_000b: ldnull - IL_000c: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() - IL_0011: tail. - IL_0013: callvirt instance string [netstandard]System.IFormattable::ToString(string, - class [netstandard]System.IFormatProvider) - IL_0018: ret + IL_0000: ldarga.s 'value' + IL_0002: ldnull + IL_0003: call class [netstandard]System.Globalization.CultureInfo [netstandard]System.Globalization.CultureInfo::get_InvariantCulture() + IL_0008: call instance string [netstandard]System.Int64::ToString(string, + class [netstandard]System.IFormatProvider) + IL_000d: ret } } From 876a45f8a45d3a74946a1744635e671b05daf83a Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Sat, 17 May 2025 13:39:26 -0400 Subject: [PATCH 4/9] Update release notes --- .../release-notes/.FSharp.Compiler.Service/10.0.100.md | 4 ++++ docs/release-notes/.FSharp.Core/10.0.100.md | 10 ++++++++++ 2 files changed, 14 insertions(+) create mode 100644 docs/release-notes/.FSharp.Core/10.0.100.md diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md index 093ff46d163..c4dca3879ee 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md @@ -3,3 +3,7 @@ * Allow `let!` and `use!` type annotations without requiring parentheses ([PR #18508](https://github.com/dotnet/fsharp/pull/18508)) * Fix find all references for F# exceptions ([PR #18565](https://github.com/dotnet/fsharp/pull/18565)) * Shorthand lambda: fix completion for chained calls and analysis for unfinished expression ([PR #18560](https://github.com/dotnet/fsharp/pull/18560)) + +### Added + +* Add support for `when 'T : Enum` library-only library-only static optimization constraint. ([PR #18546](https://github.com/dotnet/fsharp/pull/18546)) diff --git a/docs/release-notes/.FSharp.Core/10.0.100.md b/docs/release-notes/.FSharp.Core/10.0.100.md new file mode 100644 index 00000000000..2bdb77d56ab --- /dev/null +++ b/docs/release-notes/.FSharp.Core/10.0.100.md @@ -0,0 +1,10 @@ +### Fixed + +### Added + +* Enable more `string` optimizations by adding `when 'T : Enum` library-only library-only static optimization constraint. ([PR #18546](https://github.com/dotnet/fsharp/pull/18546)) + +### Changed + +### Breaking Changes + From 2dc88d594445b810212f2b7842def6b5ab33d206 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Sat, 17 May 2025 15:17:38 -0400 Subject: [PATCH 5/9] Update trimmed size --- tests/AheadOfTime/Trimming/check.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/AheadOfTime/Trimming/check.ps1 b/tests/AheadOfTime/Trimming/check.ps1 index 50a8a9e6422..f0aece70cd3 100644 --- a/tests/AheadOfTime/Trimming/check.ps1 +++ b/tests/AheadOfTime/Trimming/check.ps1 @@ -46,4 +46,4 @@ function CheckTrim($root, $tfm, $outputfile, $expected_len) { CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outputfile "FSharp.Core.dll" -expected_len 300032 # Check net8.0 trimmed assemblies -CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9150976 +CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9154048 From 1742abb3558a1a5c4ed541f57e261794f3096c90 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Sat, 17 May 2025 16:04:57 -0400 Subject: [PATCH 6/9] Update ILVerify baselines --- ...verify_FSharp.Compiler.Service_Debug_net9.0.bsl | 14 +++++++------- ...Sharp.Compiler.Service_Debug_netstandard2.0.bsl | 14 +++++++------- ...rify_FSharp.Compiler.Service_Release_net9.0.bsl | 2 +- ...arp.Compiler.Service_Release_netstandard2.0.bsl | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl index 897dd2ac22a..74547d9fef8 100644 --- a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl +++ b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl @@ -21,14 +21,14 @@ [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x00000082][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x0000008B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+MagicAssemblyResolution::ResolveAssemblyCore([FSharp.Compiler.Service]Internal.Utilities.Library.CompilationThreadToken, [FSharp.Compiler.Service]FSharp.Compiler.Text.Range, [FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, [FSharp.Compiler.Service]FSharp.Compiler.CompilerImports+TcImports, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompiler, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiConsoleOutput, string)][offset 0x00000015][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3494-805::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3494-811::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack. [IL]: Error [UnmanagedPointer]: : FSharp.Compiler.Interactive.Shell+Utilities+pointerToNativeInt@106::Invoke(object)][offset 0x00000007] Unmanaged pointers are not a verifiable type. [IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+dataTipOfReferences@2225::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000084][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-509::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-509::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-509::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000082][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-509::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000008B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-509::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000094][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-515::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-515::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-515::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000082][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-515::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000008B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-515::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000094][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.StaticLinking+TypeForwarding::followTypeForwardForILTypeRef([FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.IL+ILTypeRef)][offset 0x00000010][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CompilerOptions::getCompilerOption([FSharp.Compiler.Service]FSharp.Compiler.CompilerOptions+CompilerOption, [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1)][offset 0x000000E6][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CompilerOptions::AddPathMapping([FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, string)][offset 0x0000000B][found Char] Unexpected type on the stack. @@ -54,7 +54,7 @@ [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILPdbWriter+PortablePdbGenerator::serializeDocumentName(string)][offset 0x00000090][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILPdbWriter+pushShadowedLocals@959::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000232][found Byte] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::seekReadUntaggedIdx([FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.BinaryConstants+TableName, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+ILMetadataReader, [FSharp.Compiler.Service]FSharp.Compiler.IO.ReadOnlyByteMemory, int32&)][offset 0x0000000D][found Byte] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::openMetadataReader(string, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+BinaryFile, int32, [S.P.CoreLib]System.Tuple`8,bool,bool,bool,bool,bool,System.Tuple`5,bool,int32,int32,int32>>, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+PEReader, [FSharp.Compiler.Service]FSharp.Compiler.IO.ReadOnlyByteMemory, [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1, bool)][offset 0x00000799][found Boolean] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::openMetadataReader(string, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+BinaryFile, int32, [S.P.CoreLib]System.Tuple`8,bool,bool,bool,bool,bool,System.Tuple`5,bool,int32,int32,int32>>, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+PEReader, [FSharp.Compiler.Service]FSharp.Compiler.IO.ReadOnlyByteMemory, [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1, bool)][offset 0x000007A3][found Boolean] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader+rowKindSize@4445::Invoke([FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+RowKind)][offset 0x00000128][found Byte] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.IL::parseILVersion(string)][offset 0x0000000B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.IL::parseILVersion(string)][offset 0x00000021][found Char] Unexpected type on the stack. diff --git a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl index 077663dca21..beb556eb947 100644 --- a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl +++ b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl @@ -28,18 +28,18 @@ [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x0000008B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+FsiStdinSyphon::GetLine(string, int32)][offset 0x00000039][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+MagicAssemblyResolution::ResolveAssemblyCore([FSharp.Compiler.Service]Internal.Utilities.Library.CompilationThreadToken, [FSharp.Compiler.Service]FSharp.Compiler.Text.Range, [FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, [FSharp.Compiler.Service]FSharp.Compiler.CompilerImports+TcImports, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompiler, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiConsoleOutput, string)][offset 0x00000015][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3494-805::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3494-811::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+FsiInteractionProcessor::CompletionsForPartialLID([FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompilerState, string)][offset 0x0000001B][found Char] Unexpected type on the stack. [IL]: Error [UnmanagedPointer]: : FSharp.Compiler.Interactive.Shell+Utilities+pointerToNativeInt@106::Invoke(object)][offset 0x00000007] Unmanaged pointers are not a verifiable type. [IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+dataTipOfReferences@2225::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000084][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.AssemblyContent+traverseMemberFunctionAndValues@176::Invoke([FSharp.Compiler.Service]FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue)][offset 0x00000059][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.AssemblyContent+traverseEntity@218::GenerateNext([S.P.CoreLib]System.Collections.Generic.IEnumerable`1&)][offset 0x000000DA][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.ParsedInput+visitor@1431-6::VisitExpr([FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>, [FSharp.Compiler.Service]FSharp.Compiler.Syntax.SynExpr)][offset 0x00000605][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-509::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-509::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-509::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000082][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-509::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000008B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-509::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000094][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-515::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-515::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-515::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000082][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-515::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000008B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@922-515::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000094][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : .$Symbols+fullName@2496-1::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000015][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CreateILModule+MainModuleBuilder::ConvertProductVersionToILVersionInfo(string)][offset 0x00000011][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.StaticLinking+TypeForwarding::followTypeForwardForILTypeRef([FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.IL+ILTypeRef)][offset 0x00000010][found Char] Unexpected type on the stack. @@ -74,7 +74,7 @@ [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILPdbWriter+PortablePdbGenerator::serializeDocumentName(string)][offset 0x00000090][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILPdbWriter+pushShadowedLocals@959::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000232][found Byte] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::seekReadUntaggedIdx([FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.BinaryConstants+TableName, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+ILMetadataReader, [FSharp.Compiler.Service]FSharp.Compiler.IO.ReadOnlyByteMemory, int32&)][offset 0x0000000D][found Byte] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::openMetadataReader(string, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+BinaryFile, int32, [S.P.CoreLib]System.Tuple`8,bool,bool,bool,bool,bool,System.Tuple`5,bool,int32,int32,int32>>, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+PEReader, [FSharp.Compiler.Service]FSharp.Compiler.IO.ReadOnlyByteMemory, [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1, bool)][offset 0x00000799][found Boolean] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::openMetadataReader(string, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+BinaryFile, int32, [S.P.CoreLib]System.Tuple`8,bool,bool,bool,bool,bool,System.Tuple`5,bool,int32,int32,int32>>, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+PEReader, [FSharp.Compiler.Service]FSharp.Compiler.IO.ReadOnlyByteMemory, [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1, bool)][offset 0x000007A3][found Boolean] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader+rowKindSize@4445::Invoke([FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+RowKind)][offset 0x00000128][found Byte] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.NativeRes+VersionHelper::TryParse(string, bool, uint16, bool, [S.P.CoreLib]System.Version&)][offset 0x0000003D][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.IL::parseILVersion(string)][offset 0x0000000B][found Char] Unexpected type on the stack. diff --git a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl index 9821dc3dc1a..5ec5ff6c37f 100644 --- a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl +++ b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl @@ -75,7 +75,7 @@ [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::seekReadNestedRowUncached([FSharp.Core]Microsoft.FSharp.Core.FSharpRef`1>, int32)][offset 0x00000058][found Byte] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::seekReadGenericParamConstraintIdx([FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+ILMetadataReader, [FSharp.Compiler.Service]FSharp.Compiler.IO.ReadOnlyByteMemory, int32)][offset 0x00000025][found Byte] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::rowKindSize$cont@4446(bool, bool, bool, bool[], bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1, [FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x000000E5][found Byte] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::openMetadataReader(string, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+BinaryFile, int32, [S.P.CoreLib]System.Tuple`8,bool,bool,bool,bool,bool,System.Tuple`5,bool,int32,int32,int32>>, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+PEReader, [FSharp.Compiler.Service]FSharp.Compiler.IO.ReadOnlyByteMemory, [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1, bool)][offset 0x000006BF][found Boolean] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::openMetadataReader(string, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+BinaryFile, int32, [S.P.CoreLib]System.Tuple`8,bool,bool,bool,bool,bool,System.Tuple`5,bool,int32,int32,int32>>, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+PEReader, [FSharp.Compiler.Service]FSharp.Compiler.IO.ReadOnlyByteMemory, [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1, bool)][offset 0x000006B6][found Boolean] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader+seekReadInterfaceImpls@2263-3::Invoke(int32)][offset 0x0000002F][found Byte] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader+seekReadGenericParamConstraints@2328-2::Invoke(int32)][offset 0x0000002F][found Byte] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader+enclIdx@2357-2::Invoke(int32)][offset 0x0000002F][found Byte] Unexpected type on the stack. diff --git a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl index fd0973324e5..6fb96ea1b65 100644 --- a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl +++ b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl @@ -96,7 +96,7 @@ [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::seekReadNestedRowUncached([FSharp.Core]Microsoft.FSharp.Core.FSharpRef`1>, int32)][offset 0x00000058][found Byte] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::seekReadGenericParamConstraintIdx([FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+ILMetadataReader, [FSharp.Compiler.Service]FSharp.Compiler.IO.ReadOnlyByteMemory, int32)][offset 0x00000025][found Byte] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::rowKindSize$cont@4446(bool, bool, bool, bool[], bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, bool, [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1, [FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x000000E5][found Byte] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::openMetadataReader(string, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+BinaryFile, int32, [S.P.CoreLib]System.Tuple`8,bool,bool,bool,bool,bool,System.Tuple`5,bool,int32,int32,int32>>, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+PEReader, [FSharp.Compiler.Service]FSharp.Compiler.IO.ReadOnlyByteMemory, [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1, bool)][offset 0x000006BF][found Boolean] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader::openMetadataReader(string, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+BinaryFile, int32, [S.P.CoreLib]System.Tuple`8,bool,bool,bool,bool,bool,System.Tuple`5,bool,int32,int32,int32>>, [FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.ILBinaryReader+PEReader, [FSharp.Compiler.Service]FSharp.Compiler.IO.ReadOnlyByteMemory, [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1, bool)][offset 0x000006B6][found Boolean] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader+seekReadInterfaceImpls@2263-3::Invoke(int32)][offset 0x0000002F][found Byte] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader+seekReadGenericParamConstraints@2328-2::Invoke(int32)][offset 0x0000002F][found Byte] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.AbstractIL.ILBinaryReader+enclIdx@2357-2::Invoke(int32)][offset 0x0000002F][found Byte] Unexpected type on the stack. From f8498b18d1b3787b6dff87fac14282fbe596d6a9 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Sat, 17 May 2025 17:18:51 -0400 Subject: [PATCH 7/9] Add missing comma to `string` doc comment --- src/FSharp.Core/prim-types.fsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FSharp.Core/prim-types.fsi b/src/FSharp.Core/prim-types.fsi index d340ccf036d..f7cc97970ac 100644 --- a/src/FSharp.Core/prim-types.fsi +++ b/src/FSharp.Core/prim-types.fsi @@ -4784,7 +4784,7 @@ namespace Microsoft.FSharp.Core /// Converts the argument to a string using ToString. /// - /// For standard integer and floating point values and any type that implements IFormattable + /// For standard integer and floating point values and any type that implements IFormattable, /// ToString conversion uses CultureInfo.InvariantCulture. /// The input value. /// From 692af20f301253a0e4ba4a378582e27d9b9f50a4 Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Mon, 19 May 2025 08:22:34 -0400 Subject: [PATCH 8/9] Add `EditorBrowsableState.Never` to `SupportsWhenTEnum` --- src/FSharp.Core/prim-types.fs | 2 ++ src/FSharp.Core/prim-types.fsi | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/FSharp.Core/prim-types.fs b/src/FSharp.Core/prim-types.fs index 355c97d1ea8..df33a2e9eae 100644 --- a/src/FSharp.Core/prim-types.fs +++ b/src/FSharp.Core/prim-types.fs @@ -393,6 +393,7 @@ namespace Microsoft.FSharp.Core namespace Microsoft.FSharp.Core.CompilerServices + open System.ComponentModel open Microsoft.FSharp.Core /// @@ -400,6 +401,7 @@ namespace Microsoft.FSharp.Core.CompilerServices /// library-only static optimization constraint will recognize. /// [] + [] [] type SupportsWhenTEnum = class end diff --git a/src/FSharp.Core/prim-types.fsi b/src/FSharp.Core/prim-types.fsi index f7cc97970ac..6697e108d2c 100644 --- a/src/FSharp.Core/prim-types.fsi +++ b/src/FSharp.Core/prim-types.fsi @@ -996,6 +996,7 @@ namespace Microsoft.FSharp.Core namespace Microsoft.FSharp.Core.CompilerServices + open System.ComponentModel open Microsoft.FSharp.Core /// @@ -1003,6 +1004,7 @@ namespace Microsoft.FSharp.Core.CompilerServices /// library-only static optimization constraint will recognize. /// [] + [] [] type SupportsWhenTEnum = class end From 4d198f5cbe730a2e22660bd014d9b02c9036ce8c Mon Sep 17 00:00:00 2001 From: Brian Rourke Boll Date: Mon, 19 May 2025 19:33:49 -0400 Subject: [PATCH 9/9] Update comments --- src/FSharp.Core/prim-types.fs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/FSharp.Core/prim-types.fs b/src/FSharp.Core/prim-types.fs index df33a2e9eae..0fb2b87a06d 100644 --- a/src/FSharp.Core/prim-types.fs +++ b/src/FSharp.Core/prim-types.fs @@ -5183,10 +5183,9 @@ namespace Microsoft.FSharp.Core when 'T : nativeint = let x = (# "" value : nativeint #) in x.ToString() when 'T : unativeint = let x = (# "" value : unativeint #) in x.ToString() - // Integral types can be enum: - // It is not possible to distinguish statically between Enum and (any type of) int. For signed types we have - // to use IFormattable::ToString, as the minus sign can be overridden. Using boxing we'll print their symbolic - // value if it's an enum, e.g.: 'ConsoleKey.Backspace' gives "Backspace", rather than "8") + // These rules for signed integer types will no longer be used when built with a compiler version that + // supports `when 'T : Enum`, but we must keep them to remain compatible with compiler versions that do not. + // Once all compiler versions that do not understand `when 'T : Enum` are out of support, these four rules can be removed. when 'T : sbyte = (box value :?> IFormattable).ToString(null, CultureInfo.InvariantCulture) when 'T : int16 = (box value :?> IFormattable).ToString(null, CultureInfo.InvariantCulture) when 'T : int32 = (box value :?> IFormattable).ToString(null, CultureInfo.InvariantCulture) @@ -5228,8 +5227,17 @@ namespace Microsoft.FSharp.Core let inline string (value : 'T) = defaultString value - // Special handling for enums whose underlying type is a signed integral type. - // The runtime value may be outside the defined members of the enum, and the negative sign may be overridden. + // Special handling is required for enums, since: + // + // - The runtime value may be outside the defined members of the enum. + // - Their underlying type may be a signed integral type. + // - The negative sign may be overridden. + // + // For example: + // + // string DayOfWeek.Wednesday → "Wednesday" + // string (enum -3) → "-3" // The negative sign is culture-dependent. + // string (enum -3) → "⁒3" // E.g., the negative sign for the current culture could be overridden to "⁒". when 'T : Enum = let x = (# "" value : 'T #) in x.ToString() // Use 'T to constrain the call to the specific enum type. // For compilers that understand `when 'T : Enum`, we can safely make a constrained call on the integral type itself here.