From af0015e5cac978bef2aa10264f63c886a2532bda Mon Sep 17 00:00:00 2001 From: Don Syme Date: Mon, 22 Aug 2022 10:56:12 +0100 Subject: [PATCH] Reflection free code gen (#12960) Added --reflectionfree compiler flag to avoid %A string formatting --- src/Compiler/Checking/CheckFormatStrings.fs | 3 + src/Compiler/CodeGen/IlxGen.fs | 121 +++++++------- src/Compiler/CodeGen/IlxGen.fsi | 3 + src/Compiler/Driver/CompilerConfig.fs | 5 + src/Compiler/Driver/CompilerConfig.fsi | 3 + src/Compiler/Driver/CompilerImports.fs | 1 + src/Compiler/Driver/CompilerOptions.fs | 7 + src/Compiler/Driver/OptimizeInputs.fs | 1 + src/Compiler/FSComp.txt | 2 + src/Compiler/TypedTree/TcGlobals.fs | 3 + src/Compiler/xlf/FSComp.txt.cs.xlf | 10 ++ src/Compiler/xlf/FSComp.txt.de.xlf | 10 ++ src/Compiler/xlf/FSComp.txt.es.xlf | 10 ++ src/Compiler/xlf/FSComp.txt.fr.xlf | 10 ++ src/Compiler/xlf/FSComp.txt.it.xlf | 10 ++ src/Compiler/xlf/FSComp.txt.ja.xlf | 10 ++ src/Compiler/xlf/FSComp.txt.ko.xlf | 10 ++ src/Compiler/xlf/FSComp.txt.pl.xlf | 10 ++ src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 10 ++ src/Compiler/xlf/FSComp.txt.ru.xlf | 10 ++ src/Compiler/xlf/FSComp.txt.tr.xlf | 10 ++ src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 10 ++ src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 10 ++ src/FSharp.Build/FSharp.Build.fsproj | 2 + src/FSharp.Build/Fsc.fs | 8 + src/FSharp.Build/Fsi.fs | 4 + .../Microsoft.FSharp.NetSdk.props | 1 + src/FSharp.Build/Microsoft.FSharp.Targets | 1 + .../CompilerOptions/fsc/reflectionfree.fs | 53 ++++++ .../InequalityComparison02.fs.il.bsl | 4 +- .../FSharp.Compiler.ComponentTests.fsproj | 1 + tests/FSharp.Test.Utilities/Compiler.fs | 8 +- tests/FSharp.Test.Utilities/CompilerAssert.fs | 66 ++++---- tests/FSharp.Test.Utilities/ILChecker.fs | 158 +++++++++--------- .../fsc/help/help40.437.1033.bsl | 2 + .../fsi/exename/help40.437.1033.bsl | 2 + .../fsi/help/help40-nologo.437.1033.bsl | 2 + .../fsi/help/help40.437.1033.bsl | 2 + 38 files changed, 424 insertions(+), 169 deletions(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/reflectionfree.fs diff --git a/src/Compiler/Checking/CheckFormatStrings.fs b/src/Compiler/Checking/CheckFormatStrings.fs index 31471ba32b2..8652e305761 100644 --- a/src/Compiler/Checking/CheckFormatStrings.fs +++ b/src/Compiler/Checking/CheckFormatStrings.fs @@ -444,6 +444,9 @@ let parseFormatStringInternal parseLoop ((posi, NewInferenceType g) :: acc) (i, fragLine, startFragCol) fragments | 'A' -> + if g.useReflectionFreeCodeGen then + failwith (FSComp.SR.forPercentAInReflectionFreeCode()) + match info.numPrefixIfPos with | None // %A has BindingFlags=Public, %+A has BindingFlags=Public | NonPublic | Some '+' -> diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 753e7772676..19f6de28fd1 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -288,6 +288,9 @@ type IlxGenOptions = /// storage, even though 'it' is not logically mutable isInteractiveItExpr: bool + /// Suppress ToString emit + useReflectionFreeCodeGen: bool + /// Whenever possible, use callvirt instead of call alwaysCallVirt: bool } @@ -10449,64 +10452,65 @@ and GenPrintingMethod cenv eenv methName ilThisTy m = let g = cenv.g [ - match (eenv.valsInScope.TryFind g.sprintf_vref.Deref, eenv.valsInScope.TryFind g.new_format_vref.Deref) with - | Some (Lazy (Method (_, _, sprintfMethSpec, _, _, _, _, _, _, _, _, _))), - Some (Lazy (Method (_, _, newFormatMethSpec, _, _, _, _, _, _, _, _, _))) -> - // The type returned by the 'sprintf' call - let funcTy = EraseClosures.mkILFuncTy cenv.ilxPubCloEnv ilThisTy g.ilg.typ_String - - // Give the instantiation of the printf format object, i.e. a Format`5 object compatible with StringFormat - let newFormatMethSpec = - mkILMethSpec ( - newFormatMethSpec.MethodRef, - AsObject, - [ // 'T -> string' - funcTy - // rest follow from 'StringFormat' - GenUnitTy cenv eenv m - g.ilg.typ_String - g.ilg.typ_String - ilThisTy - ], - [] - ) - - // Instantiate with our own type - let sprintfMethSpec = - mkILMethSpec (sprintfMethSpec.MethodRef, AsObject, [], [ funcTy ]) + if not g.useReflectionFreeCodeGen then + match (eenv.valsInScope.TryFind g.sprintf_vref.Deref, eenv.valsInScope.TryFind g.new_format_vref.Deref) with + | Some (Lazy (Method (_, _, sprintfMethSpec, _, _, _, _, _, _, _, _, _))), + Some (Lazy (Method (_, _, newFormatMethSpec, _, _, _, _, _, _, _, _, _))) -> + // The type returned by the 'sprintf' call + let funcTy = EraseClosures.mkILFuncTy cenv.ilxPubCloEnv ilThisTy g.ilg.typ_String + + // Give the instantiation of the printf format object, i.e. a Format`5 object compatible with StringFormat + let newFormatMethSpec = + mkILMethSpec ( + newFormatMethSpec.MethodRef, + AsObject, + [ // 'T -> string' + funcTy + // rest follow from 'StringFormat' + GenUnitTy cenv eenv m + g.ilg.typ_String + g.ilg.typ_String + ilThisTy + ], + [] + ) - // Here's the body of the method. Call printf, then invoke the function it returns - let callInstrs = - EraseClosures.mkCallFunc - cenv.ilxPubCloEnv - (fun _ -> 0us) - eenv.tyenv.Count - Normalcall - (Apps_app(ilThisTy, Apps_done g.ilg.typ_String)) - - let ilInstrs = - [ // load the hardwired format string - I_ldstr "%+A" - // make the printf format object - mkNormalNewobj newFormatMethSpec - // call sprintf - mkNormalCall sprintfMethSpec - // call the function returned by sprintf - mkLdarg0 - if ilThisTy.Boxity = ILBoxity.AsValue then - mkNormalLdobj ilThisTy - yield! callInstrs - ] + // Instantiate with our own type + let sprintfMethSpec = + mkILMethSpec (sprintfMethSpec.MethodRef, AsObject, [], [ funcTy ]) + + // Here's the body of the method. Call printf, then invoke the function it returns + let callInstrs = + EraseClosures.mkCallFunc + cenv.ilxPubCloEnv + (fun _ -> 0us) + eenv.tyenv.Count + Normalcall + (Apps_app(ilThisTy, Apps_done g.ilg.typ_String)) + + let ilInstrs = + [ // load the hardwired format string + I_ldstr "%+A" + // make the printf format object + mkNormalNewobj newFormatMethSpec + // call sprintf + mkNormalCall sprintfMethSpec + // call the function returned by sprintf + mkLdarg0 + if ilThisTy.Boxity = ILBoxity.AsValue then + mkNormalLdobj ilThisTy + yield! callInstrs + ] - let ilMethodBody = - mkMethodBody (true, [], 2, nonBranchingInstrsToCode ilInstrs, None, eenv.imports) + let ilMethodBody = + mkMethodBody (true, [], 2, nonBranchingInstrsToCode ilInstrs, None, eenv.imports) - let mdef = - mkILNonGenericVirtualInstanceMethod (methName, ILMemberAccess.Public, [], mkILReturn g.ilg.typ_String, ilMethodBody) + let mdef = + mkILNonGenericVirtualInstanceMethod (methName, ILMemberAccess.Public, [], mkILReturn g.ilg.typ_String, ilMethodBody) - let mdef = mdef.With(customAttrs = mkILCustomAttrs [ g.CompilerGeneratedAttribute ]) - yield mdef - | _ -> () + let mdef = mdef.With(customAttrs = mkILCustomAttrs [ g.CompilerGeneratedAttribute ]) + yield mdef + | _ -> () ] and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) = @@ -10646,6 +10650,8 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) = let tyconRepr = tycon.TypeReprInfo + let reprAccess = ComputeMemberAccess hiddenRepr + // DebugDisplayAttribute gets copied to the subtypes generated as part of DU compilation let debugDisplayAttrs, normalAttrs = tycon.Attribs @@ -10656,7 +10662,10 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) = |> List.partition (fun a -> IsSecurityAttribute g cenv.amap cenv.casApplied a m) let generateDebugDisplayAttribute = - not g.compilingFSharpCore && tycon.IsUnionTycon && isNil debugDisplayAttrs + not g.useReflectionFreeCodeGen + && not g.compilingFSharpCore + && tycon.IsUnionTycon + && isNil debugDisplayAttrs let generateDebugProxies = not (tyconRefEq g tcref g.unit_tcr_canon) @@ -10687,8 +10696,6 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) = yield! ilDebugDisplayAttributes ] - let reprAccess = ComputeMemberAccess hiddenRepr - let ilTypeDefKind = match tyconRepr with | TFSharpObjectRepr o -> diff --git a/src/Compiler/CodeGen/IlxGen.fsi b/src/Compiler/CodeGen/IlxGen.fsi index 8d6ea6d8a65..d68463e9ca7 100644 --- a/src/Compiler/CodeGen/IlxGen.fsi +++ b/src/Compiler/CodeGen/IlxGen.fsi @@ -54,6 +54,9 @@ type internal IlxGenOptions = /// storage, even though 'it' is not logically mutable isInteractiveItExpr: bool + /// Suppress ToString emit + useReflectionFreeCodeGen: bool + /// Indicates that, whenever possible, use callvirt instead of call alwaysCallVirt: bool } diff --git a/src/Compiler/Driver/CompilerConfig.fs b/src/Compiler/Driver/CompilerConfig.fs index 88db6a031c2..38c67e917f6 100644 --- a/src/Compiler/Driver/CompilerConfig.fs +++ b/src/Compiler/Driver/CompilerConfig.fs @@ -527,6 +527,9 @@ type TcConfigBuilder = /// If true, strip away data that would not be of use to end users, but is useful to us for debugging mutable noDebugAttributes: bool + /// If true, do not emit ToString implementations for unions, records, structs, exceptions + mutable useReflectionFreeCodeGen: bool + /// If true, indicates all type checking and code generation is in the context of fsi.exe isInteractive: bool @@ -730,6 +733,7 @@ type TcConfigBuilder = pause = false alwaysCallVirt = true noDebugAttributes = false + useReflectionFreeCodeGen = false emitDebugInfoInQuotations = false exename = None shadowCopyReferences = false @@ -1279,6 +1283,7 @@ type TcConfig private (data: TcConfigBuilder, validate: bool) = member _.pause = data.pause member _.alwaysCallVirt = data.alwaysCallVirt member _.noDebugAttributes = data.noDebugAttributes + member _.useReflectionFreeCodeGen = data.useReflectionFreeCodeGen member _.isInteractive = data.isInteractive member _.isInvalidationSupported = data.isInvalidationSupported member _.emitDebugInfoInQuotations = data.emitDebugInfoInQuotations diff --git a/src/Compiler/Driver/CompilerConfig.fsi b/src/Compiler/Driver/CompilerConfig.fsi index 0524261c5c8..0ac2d8a0e86 100644 --- a/src/Compiler/Driver/CompilerConfig.fsi +++ b/src/Compiler/Driver/CompilerConfig.fsi @@ -431,6 +431,8 @@ type TcConfigBuilder = mutable noDebugAttributes: bool + mutable useReflectionFreeCodeGen: bool + /// If true, indicates all type checking and code generation is in the context of fsi.exe isInteractive: bool @@ -740,6 +742,7 @@ type TcConfig = member alwaysCallVirt: bool member noDebugAttributes: bool + member useReflectionFreeCodeGen: bool /// If true, indicates all type checking and code generation is in the context of fsi.exe member isInteractive: bool diff --git a/src/Compiler/Driver/CompilerImports.fs b/src/Compiler/Driver/CompilerImports.fs index 60722df6c9d..7d453dace33 100644 --- a/src/Compiler/Driver/CompilerImports.fs +++ b/src/Compiler/Driver/CompilerImports.fs @@ -2410,6 +2410,7 @@ and [] TcImports tcConfig.implicitIncludeDir, tcConfig.mlCompatibility, tcConfig.isInteractive, + tcConfig.useReflectionFreeCodeGen, tryFindSysTypeCcu, tcConfig.emitDebugInfoInQuotations, tcConfig.noDebugAttributes, diff --git a/src/Compiler/Driver/CompilerOptions.fs b/src/Compiler/Driver/CompilerOptions.fs index 90fa3728838..c2e89e7c2c2 100644 --- a/src/Compiler/Driver/CompilerOptions.fs +++ b/src/Compiler/Driver/CompilerOptions.fs @@ -1054,6 +1054,13 @@ let codeGenerationFlags isFsi (tcConfigB: TcConfigBuilder) = Some(FSComp.SR.optsCrossoptimize ()) ) + CompilerOption( + "reflectionfree", + tagNone, + OptionUnit(fun () -> tcConfigB.useReflectionFreeCodeGen <- true), + None, + Some(FSComp.SR.optsReflectionFree ()) + ) ] if isFsi then debug @ codegen else debug @ embed @ codegen diff --git a/src/Compiler/Driver/OptimizeInputs.fs b/src/Compiler/Driver/OptimizeInputs.fs index 299bb1a8886..0696758780b 100644 --- a/src/Compiler/Driver/OptimizeInputs.fs +++ b/src/Compiler/Driver/OptimizeInputs.fs @@ -251,6 +251,7 @@ let GenerateIlxCode mainMethodInfo = mainMethodInfo ilxBackend = ilxBackend fsiMultiAssemblyEmit = tcConfig.fsiMultiAssemblyEmit + useReflectionFreeCodeGen = tcConfig.useReflectionFreeCodeGen isInteractive = tcConfig.isInteractive isInteractiveItExpr = isInteractiveItExpr alwaysCallVirt = tcConfig.alwaysCallVirt diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 19f7d040b46..c749444f32c 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -235,6 +235,7 @@ forLIsUnnecessary,"The 'l' or 'L' in this format specifier is unnecessary. In F# forHIsUnnecessary,"The 'h' or 'H' in this format specifier is unnecessary. You can use %%d, %%x, %%o or %%u instead, which are overloaded to work with all basic integer types." forDoesNotSupportPrefixFlag,"'%s' does not support prefix '%s' flag" forBadFormatSpecifierGeneral,"Bad format specifier: '%s'" +forPercentAInReflectionFreeCode,"The '%%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection." elSysEnvExitDidntExit,"System.Environment.Exit did not exit" elDeprecatedOperator,"The treatment of this operator is now handled directly by the F# compiler and its meaning cannot be redefined" 405,chkProtectedOrBaseCalled,"A protected member is called or 'base' is being used. This is only allowed in the direct implementation of members since they could escape their object scope." @@ -873,6 +874,7 @@ optsRefOnly,"Produce a reference assembly, instead of a full assembly, as the pr optsRefOut,"Produce a reference assembly with the specified file path." optsPathMap,"Maps physical paths to source path names output by the compiler" optsCrossoptimize,"Enable or disable cross-module optimizations" +optsReflectionFree,"Disable implicit generation of constructs using reflection" optsWarnaserrorPM,"Report all warnings as errors" optsWarnaserror,"Report specific warnings as errors" optsWarn,"Set a warning level (0-5)" diff --git a/src/Compiler/TypedTree/TcGlobals.fs b/src/Compiler/TypedTree/TcGlobals.fs index a38d87199e0..9ab7cf2f723 100755 --- a/src/Compiler/TypedTree/TcGlobals.fs +++ b/src/Compiler/TypedTree/TcGlobals.fs @@ -191,6 +191,7 @@ type TcGlobals( directoryToResolveRelativePaths, mlCompatibility: bool, isInteractive: bool, + useReflectionFreeCodeGen: bool, // The helper to find system types amongst referenced DLLs tryFindSysTypeCcu, emitDebugInfoInQuotations: bool, @@ -1004,6 +1005,8 @@ type TcGlobals( member _.compilingFSharpCore = compilingFSharpCore + member _.useReflectionFreeCodeGen = useReflectionFreeCodeGen + member _.mlCompatibility = mlCompatibility member _.emitDebugInfoInQuotations = emitDebugInfoInQuotations diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index d02d7fb9305..8c2f6fe5f54 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -327,6 +327,11 @@ Interpolované řetězce, které se používají jako typ IFormattable nebo FormattableString, nemůžou používat specifikátory %. Použít je možné jen interpolované výrazy ve stylu .NET, třeba {{expr}}, {{expr,3}} nebo {{expr:N5}}. + + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + + - {0} - {0} @@ -497,6 +502,11 @@ Vytvoří referenční sestavení se zadanou cestou k souboru. + + Disable implicit generation of constructs using reflection + Disable implicit generation of constructs using reflection + + Supported language versions: Podporované jazykové verze: diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index a61342cb5b8..68e2a3f1eb7 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -327,6 +327,11 @@ Interpolierte Zeichenfolgen, die als Typ "IFormattable" oder "FormattableString" verwendet werden, dürfen keine Spezifizierer vom Typ "%" verwenden. Es dürfen nur Interpolanten im .NET-Format wie "{{expr}}", "{{expr,3}}" oder "{{expr:N5}}" verwendet werden. + + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + + - {0} – {0} @@ -497,6 +502,11 @@ Erstellen Sie eine Referenzassembly mit dem angegebenen Dateipfad. + + Disable implicit generation of constructs using reflection + Disable implicit generation of constructs using reflection + + Supported language versions: Unterstützte Sprachversionen: diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 03d20b0376a..bea08c9779f 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -327,6 +327,11 @@ Las cadenas interpoladas que se usan como tipo IFormattable o FormattableString no pueden usar los especificadores "%"; solo pueden utilizar operandos de interpolación de estilo .NET, como "{{expr}}", "{{expr,3}}" o "{{expr:N5}}". + + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + + - {0} - {0} @@ -497,6 +502,11 @@ Genera un ensamblado de referencia con la ruta de acceso de archivo especificada. + + Disable implicit generation of constructs using reflection + Disable implicit generation of constructs using reflection + + Supported language versions: Versiones de lenguaje admitidas: diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 2cf068a9b87..227a9b0bd32 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -327,6 +327,11 @@ Les chaînes interpolées utilisées en tant que type IFormattable ou FormattableString ne peuvent pas utiliser les spécificateurs '%'. Seuls les spécificateurs de style .NET tels que '{{expr}}', '{{expr,3}}' et '{{expr:N5}}' peuvent être utilisés. + + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + + - {0} - {0} @@ -497,6 +502,11 @@ Produire un assembly de référence avec le chemin de fichier spécifié + + Disable implicit generation of constructs using reflection + Disable implicit generation of constructs using reflection + + Supported language versions: Versions linguistiques prises en charge : diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 60c7d9e259e..4ebe76e823c 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -327,6 +327,11 @@ Nelle stringhe interpolate usate come tipo IFormattable o FormattableString non è possibile usare gli identificatori '%', ma è possibile usare solo interpolanti di tipo .NET, come '{{expr}}', '{{expr,3}}' o '{{expr:N5}}'. + + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + + - {0} - {0} @@ -497,6 +502,11 @@ Genera un assembly di riferimento con il percorso file specificato. + + Disable implicit generation of constructs using reflection + Disable implicit generation of constructs using reflection + + Supported language versions: Versioni del linguaggio supportate: diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index fea133758e8..3d330529d59 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -327,6 +327,11 @@ IFormattable 型または FormattableString 型として使用される補間された文字列では、'%' 指定子を使用できません。'{{expr}}'、'{{expr,3}}'、'{{expr:N5}}' などの .NET 形式の補間のみ使用できます。 + + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + + - {0} - {0} @@ -497,6 +502,11 @@ 指定されたファイル パスを使用して参照アセンブリを生成します。 + + Disable implicit generation of constructs using reflection + Disable implicit generation of constructs using reflection + + Supported language versions: サポートされる言語バージョン: diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 8d54742fe3f..fb1cc618ecb 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -327,6 +327,11 @@ 형식 IFormattable 또는 형식 FormattableString으로 사용된 보간 문자열은 '%' 지정자를 사용할 수 없으며 '{{expr}}', '{{expr,3}}' 또는 '{{expr:N5}}' 등의 .NET 스타일 인터폴란드를 사용할 수 있습니다. + + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + + - {0} - {0} @@ -497,6 +502,11 @@ 지정된 파일 경로를 사용하여 참조 어셈블리를 생성합니다. + + Disable implicit generation of constructs using reflection + Disable implicit generation of constructs using reflection + + Supported language versions: 지원되는 언어 버전: diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index dd2f285ff2b..d8e30e88c62 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -327,6 +327,11 @@ W interpolowanych ciągach używanych jako typ IFormattable lub FormattableString nie można używać specyfikatorów „%”. Można używać tylko operatorów interpolacji, takich jak „{{expr}}”, „{{expr,3}}” lub „{{expr:N5}}”. + + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + + - {0} - {0} @@ -497,6 +502,11 @@ Utwórz zestaw odwołania z określoną ścieżką pliku. + + Disable implicit generation of constructs using reflection + Disable implicit generation of constructs using reflection + + Supported language versions: Obsługiwane wersje językowe: diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index b5f0f00f51c..aeeba1d4271 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -327,6 +327,11 @@ As cadeias de caracteres interpoladas usadas como tipo IFormattable ou tipo FormattableString não podem usar especificadores '%'. Apenas interpoladores de estilo .NET, como '{{expr}}', '{{expr,3}}' ou '{{expr:N5}}' podem ser usados. + + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + + - {0} - {0} @@ -497,6 +502,11 @@ Produza um assembly de referência com o caminho de arquivo especificado. + + Disable implicit generation of constructs using reflection + Disable implicit generation of constructs using reflection + + Supported language versions: Versões de linguagens com suporte: diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 5eb7751b144..5959290379e 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -327,6 +327,11 @@ Интерполированные строки, используемые в качестве типа IFormattable или FormattableString, не могут использовать описатели "%". Можно использовать только описатели в стиле .NET, такие как "{{expr}}", "{{expr,3}}" или "{{expr:N5}}". + + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + + - {0} - {0} @@ -497,6 +502,11 @@ Создайте базовую сборку с указанным путем к файлу. + + Disable implicit generation of constructs using reflection + Disable implicit generation of constructs using reflection + + Supported language versions: Поддерживаемые языковые версии: diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 7d746dd8637..37583dc87c5 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -327,6 +327,11 @@ IFormattable veya FormattableString türü olarak kullanılan düz metin arasına kod eklenmiş dizeler '%' belirticilerini kullanamaz. Yalnızca '{{expr}}', '{{expr,3}}' veya '{{expr:N5}}' gibi .NET stili düz metin arasına kod ekleme işlemleri kullanılabilir. + + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + + - {0} - {0} @@ -497,6 +502,11 @@ Belirtilen dosya yoluna sahip bir başvuru bütünleştirilmiş kodu üretin. + + Disable implicit generation of constructs using reflection + Disable implicit generation of constructs using reflection + + Supported language versions: Desteklenen dil sürümleri: diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index be235905319..a627b09742f 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -327,6 +327,11 @@ 作为类型 IFormattable 或类型 FormattableString 使用的内插字符串不能使用 "%" 说明符,只能使用 .NET 样式的插植,如 "{{expr}}"、"{{expr,3}}" 或 "{{expr:N5}}"。 + + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + + - {0} - {0} @@ -497,6 +502,11 @@ 生成具有指定文件路径的引用程序集。 + + Disable implicit generation of constructs using reflection + Disable implicit generation of constructs using reflection + + Supported language versions: 支持的语言版本: diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index c990e70697b..dd69d33f792 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -327,6 +327,11 @@ 用作類型 IFormattable 或類型 FormattableString 的插補字串不能使用 '%' 指定名稱,只能使用 .NET 樣式的插補值,例如 '{{expr}}'、'{{expr,3}}' 或 '{{expr:N5}}'。 + + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection. + + - {0} - {0} @@ -497,6 +502,11 @@ 使用指定的檔案路徑產生參考組件。 + + Disable implicit generation of constructs using reflection + Disable implicit generation of constructs using reflection + + Supported language versions: 支援的語言版本: diff --git a/src/FSharp.Build/FSharp.Build.fsproj b/src/FSharp.Build/FSharp.Build.fsproj index 9d67b00e5bc..6d4e62518f0 100644 --- a/src/FSharp.Build/FSharp.Build.fsproj +++ b/src/FSharp.Build/FSharp.Build.fsproj @@ -12,6 +12,8 @@ $(DefineConstants);LOCALIZATION_FSBUILD NU1701;FS0075 true + 5.0 + 6.0 6.0 Debug;Release;Proto diff --git a/src/FSharp.Build/Fsc.fs b/src/FSharp.Build/Fsc.fs index 9529eade6bb..909354e83e7 100644 --- a/src/FSharp.Build/Fsc.fs +++ b/src/FSharp.Build/Fsc.fs @@ -90,6 +90,7 @@ type public Fsc() as this = let mutable vserrors: bool = false let mutable vslcid: string MaybeNull = null let mutable utf8output: bool = false + let mutable useReflectionFreeCodeGen: bool = false /// Trim whitespace ... spaces, tabs, newlines,returns, Double quotes and single quotes let wsCharsToTrim = [| ' '; '\t'; '\"'; '\'' |] @@ -294,6 +295,9 @@ type public Fsc() as this = if utf8output then builder.AppendSwitch("--utf8output") + if useReflectionFreeCodeGen then + builder.AppendSwitch("--reflectionfree") + // When building using the fsc task, always emit the "fullpaths" flag to make the output easier // for the user to parse builder.AppendSwitch("--fullpaths") @@ -598,6 +602,10 @@ type public Fsc() as this = with get () = utf8output and set (p) = utf8output <- p + member _.ReflectionFree + with get () = useReflectionFreeCodeGen + and set (p) = useReflectionFreeCodeGen <- p + member _.SubsystemVersion with get () = subsystemVersion and set (p) = subsystemVersion <- p diff --git a/src/FSharp.Build/Fsi.fs b/src/FSharp.Build/Fsi.fs index 76dec9c78e4..dd0ccff9754 100644 --- a/src/FSharp.Build/Fsi.fs +++ b/src/FSharp.Build/Fsi.fs @@ -64,6 +64,7 @@ type public Fsi() as this = let mutable warningLevel: string MaybeNull = null let mutable vslcid: string MaybeNull = null let mutable utf8output: bool = false + let mutable useReflectionFreeCodeGen: bool = false // See bug 6483; this makes parallel build faster, and is fine to set unconditionally do this.YieldDuringToolExecution <- true @@ -133,6 +134,9 @@ type public Fsi() as this = if utf8output then builder.AppendSwitch("--utf8output") + if useReflectionFreeCodeGen then + builder.AppendSwitch("--reflectionfree") + builder.AppendSwitch("--fullpaths") builder.AppendSwitch("--flaterrors") diff --git a/src/FSharp.Build/Microsoft.FSharp.NetSdk.props b/src/FSharp.Build/Microsoft.FSharp.NetSdk.props index f6de7d70481..b2252e9e136 100644 --- a/src/FSharp.Build/Microsoft.FSharp.NetSdk.props +++ b/src/FSharp.Build/Microsoft.FSharp.NetSdk.props @@ -42,6 +42,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and 3239;$(WarningsAsErrors) true true + false diff --git a/src/FSharp.Build/Microsoft.FSharp.Targets b/src/FSharp.Build/Microsoft.FSharp.Targets index 22d010e10db..26d9a170882 100644 --- a/src/FSharp.Build/Microsoft.FSharp.Targets +++ b/src/FSharp.Build/Microsoft.FSharp.Targets @@ -344,6 +344,7 @@ this file. LCID="$(LCID)" NoFramework="true" Optimize="$(Optimize)" + ReflectionFree="$(ReflectionFree)" OtherFlags="$(FscOtherFlags)" OutputAssembly="@(IntermediateAssembly)" OutputRefAssembly="@(IntermediateRefAssembly)" diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/reflectionfree.fs b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/reflectionfree.fs new file mode 100644 index 00000000000..2cffacf65cf --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/CompilerOptions/fsc/reflectionfree.fs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Compiler.ComponentTests.CompilerOptions.ReflectionFree + +open Xunit +open FSharp.Test.Compiler + +[] +let ``Gives error when %A is used`` () = + FSharp """printfn "Hello, %A" "world" """ + |> asExe + |> withOptions [ "--reflectionfree" ] + |> compile + |> shouldFail + |> withDiagnostics [ + Error 741, Line 1, Col 9, Line 1, Col 20, + "Unable to parse format string 'The '%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection.'" ] + +let someCode = + FSharp """ + module Test + + type MyRecord = { A: int } + type MyUnion = A of int | B of string + type MyClass() = member val A = 42 + + let poke thing = $"Thing says: {thing}" + + [] + let doStuff _ = + poke { A = 3 } |> printfn "%s" + poke <| B "foo" |> printfn "%s" + poke <| MyClass() |> printfn "%s" + 0 + """ + +[] +let ``Records and DUs don't have generated ToString`` () = + someCode + |> withOptions [ "--reflectionfree" ] + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains "Thing says: Test+MyRecord" + |> withStdOutContains "Thing says: Test+MyUnion+B" + |> withStdOutContains "Thing says: Test+MyClass" + +[] +let ``No debug display attribute`` () = + someCode + |> withOptions [ "--reflectionfree" ] + |> compile + |> shouldSucceed + |> verifyILNotPresent [ "[runtime]System.Diagnostics.DebuggerDisplayAttribute" ] diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/InequalityComparison/InequalityComparison02.fs.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/InequalityComparison/InequalityComparison02.fs.il.bsl index 84f29017fb8..f25d03e0939 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/InequalityComparison/InequalityComparison02.fs.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/InequalityComparison/InequalityComparison02.fs.il.bsl @@ -51,11 +51,11 @@ .class public abstract auto ansi sealed InequalityComparison02 extends [mscorlib]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 ) + .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 bool f2(int32 x, int32 y) cil managed { - .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 ) + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationArgumentCountsAttribute::.ctor(int32[]) = ( 01 00 02 00 00 00 01 00 00 00 01 00 00 00 00 00 ) // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.0 diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 9cb7f248c25..d17427244c0 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -179,6 +179,7 @@ + diff --git a/tests/FSharp.Test.Utilities/Compiler.fs b/tests/FSharp.Test.Utilities/Compiler.fs index 020ebe9363c..d227cc1630e 100644 --- a/tests/FSharp.Test.Utilities/Compiler.fs +++ b/tests/FSharp.Test.Utilities/Compiler.fs @@ -893,14 +893,18 @@ module rec Compiler = cUnit - let verifyIL (il: string list) (result: CompilationResult) : unit = + let private doILCheck func (il: string list) result = match result with | CompilationResult.Success s -> match s.OutputPath with | None -> failwith "Operation didn't produce any output!" - | Some p -> ILChecker.checkIL p il + | Some p -> func p il | CompilationResult.Failure _ -> failwith "Result should be \"Success\" in order to get IL." + let verifyIL = doILCheck ILChecker.checkIL + + let verifyILNotPresent = doILCheck ILChecker.checkILNotPresent + let verifyILBinary (il: string list) (dll: string)= ILChecker.checkIL dll il let private verifyFSILBaseline (baseline: Baseline option) (result: CompilationOutput) : unit = diff --git a/tests/FSharp.Test.Utilities/CompilerAssert.fs b/tests/FSharp.Test.Utilities/CompilerAssert.fs index daab582a61b..48bd3047cba 100644 --- a/tests/FSharp.Test.Utilities/CompilerAssert.fs +++ b/tests/FSharp.Test.Utilities/CompilerAssert.fs @@ -254,6 +254,11 @@ module rec CompilerAssertHelpers = yield Array.empty |] + let executeAssemblyEntryPoint (asm: Assembly) = + let entryPoint = asm.EntryPoint + let args = mkDefaultArgs entryPoint + captureConsoleOutputs (fun () -> entryPoint.Invoke(Unchecked.defaultof, args) |> ignore) + #if NETCOREAPP let executeBuiltApp assembly deps = let ctxt = AssemblyLoadContext("ContextName", true) @@ -264,10 +269,7 @@ module rec CompilerAssertHelpers = |> Option.map ctxt.LoadFromAssemblyPath |> Option.defaultValue null) - let asm = ctxt.LoadFromAssemblyPath(assembly) - let entryPoint = asm.EntryPoint - let args = mkDefaultArgs entryPoint - (entryPoint.Invoke(Unchecked.defaultof, args)) |> ignore + ctxt.LoadFromAssemblyPath assembly |> executeAssemblyEntryPoint finally ctxt.Unload() #else @@ -281,10 +283,8 @@ module rec CompilerAssertHelpers = |> Option.bind (fun x -> if FileSystem.FileExistsShim x then Some x else None) |> Option.map Assembly.LoadFile |> Option.defaultValue null)) - let asm = Assembly.LoadFrom(assemblyPath) - let entryPoint = asm.EntryPoint - let args = mkDefaultArgs entryPoint - (entryPoint.Invoke(Unchecked.defaultof, args)) |> ignore + + Assembly.LoadFrom assemblyPath |> executeAssemblyEntryPoint let adSetup = let setup = new System.AppDomainSetup () @@ -297,7 +297,7 @@ module rec CompilerAssertHelpers = let worker = use _ = new AlreadyLoadedAppDomainResolver() (ad.CreateInstanceFromAndUnwrap(typeof.Assembly.CodeBase, typeof.FullName)) :?> Worker - worker.ExecuteTestCase assembly (deps |> Array.ofList) |>ignore + worker.ExecuteTestCase assembly (deps |> Array.ofList) #endif let defaultProjectOptions = @@ -515,34 +515,39 @@ module rec CompilerAssertHelpers = outputDirectory.Create() compileCompilationAux outputDirectory (ResizeArray()) ignoreWarnings cmpl - let executeBuiltAppAndReturnResult (outputFilePath: string) (deps: string list) : (int * string * string) = + let captureConsoleOutputs (func: unit -> unit) = let out = Console.Out let err = Console.Error let stdout = StringBuilder () let stderr = StringBuilder () - let outWriter = new StringWriter (stdout) - let errWriter = new StringWriter (stderr) - - let mutable exitCode = 0 + use outWriter = new StringWriter (stdout) + use errWriter = new StringWriter (stderr) - try + let succeeded, exn = try - Console.SetOut(outWriter) - Console.SetError(errWriter) - (executeBuiltApp outputFilePath deps) |> ignore - with e -> - let errorMessage = if e.InnerException <> null then (e.InnerException.ToString()) else (e.ToString()) - stderr.Append (errorMessage) |> ignore - exitCode <- -1 - finally - Console.SetOut(out) - Console.SetError(err) - outWriter.Close() - errWriter.Close() + try + Console.SetOut outWriter + Console.SetError errWriter + func () + true, None + with e -> + let errorMessage = if e.InnerException <> null then e.InnerException.ToString() else e.ToString() + stderr.Append errorMessage |> ignore + false, Some e + finally + Console.SetOut out + Console.SetError err + outWriter.Close() + errWriter.Close() + + succeeded, stdout.ToString(), stderr.ToString(), exn - (exitCode, stdout.ToString(), stderr.ToString()) + let executeBuiltAppAndReturnResult (outputFilePath: string) (deps: string list) : (int * string * string) = + let succeeded, stdout, stderr, _ = executeBuiltApp outputFilePath deps + let exitCode = if succeeded then 0 else -1 + exitCode, stdout, stderr let executeBuiltAppNewProcessAndReturnResult (outputFilePath: string) : (int * string * string) = #if !NETCOREAPP @@ -582,7 +587,7 @@ type CompilerAssert private () = if errors.Length > 0 then Assert.Fail (sprintf "Compile had warnings and/or errors: %A" errors) - executeBuiltApp outputExe [] + executeBuiltApp outputExe [] |> ignore ) static let compileLibraryAndVerifyILWithOptions options (source: SourceCodeFileKind) (f: ILVerifier -> unit) = @@ -672,7 +677,8 @@ Updated automatically, please check diffs in your pull request, changes must be Assert.Fail errors onOutput output else - executeBuiltApp outputFilePath deps) + let _succeeded, _stdout, _stderr, exn = executeBuiltApp outputFilePath deps + exn |> Option.iter raise) static member ExecutionHasOutput(cmpl: Compilation, expectedOutput: string) = CompilerAssert.Execute(cmpl, newProcess = true, onOutput = (fun output -> Assert.AreEqual(expectedOutput, output, sprintf "'%s' = '%s'" expectedOutput output))) diff --git a/tests/FSharp.Test.Utilities/ILChecker.fs b/tests/FSharp.Test.Utilities/ILChecker.fs index a0b1ddefdfe..546deb0a2e6 100644 --- a/tests/FSharp.Test.Utilities/ILChecker.fs +++ b/tests/FSharp.Test.Utilities/ILChecker.fs @@ -4,7 +4,6 @@ namespace FSharp.Test open System open System.IO -open System.Diagnostics open System.Text.RegularExpressions open NUnit.Framework @@ -30,14 +29,48 @@ module ILChecker = (fun me -> String.Empty) ) - let private checkILPrim ildasmArgs dllFilePath expectedIL = + let private normalizeILText assemblyName (ilCode: string) = + let blockComments = @"/\*(.*?)\*/" + let lineComments = @"//(.*?)\r?\n" + let lineCommentsEof = @"//(.*?)$" + let strings = @"""((\\[^\n]|[^""\n])*)""" + let verbatimStrings = @"@(""[^""]*"")+" + let stripComments (text:string) = + Regex.Replace(text, + $"{blockComments}|{lineComments}|{lineCommentsEof}|{strings}|{verbatimStrings}", + (fun me -> + if (me.Value.StartsWith("/*") || me.Value.StartsWith("//")) then + if me.Value.StartsWith("//") then Environment.NewLine else String.Empty + else + me.Value), RegexOptions.Singleline) + |> filterSpecialComment + + let replace input (pattern, replacement: string) = Regex.Replace(input, pattern, replacement, RegexOptions.Singleline) + + let unifyRuntimeAssemblyName ilCode = + List.fold replace ilCode [ + "\[System\.Runtime\]|\[System\.Console\]|\[System\.Runtime\.Extensions\]|\[mscorlib\]|\[System\.Memory\]", "[runtime]" + "(\.assembly extern (System\.Runtime|System\.Console|System\.Runtime\.Extensions|mscorlib|System\.Memory)){1}([^\}]*)\}", ".assembly extern runtime { }" + "(\.assembly extern (FSharp.Core)){1}([^\}]*)\}", ".assembly extern FSharp.Core { }" ] + + let unifyImageBase ilCode = replace ilCode ("\.imagebase\s*0x\d*", ".imagebase {value}") + + let unifyingAssemblyNames (text: string) = + match assemblyName with + | Some name -> text.Replace(name, "assembly") + | None -> text + |> unifyRuntimeAssemblyName + |> unifyImageBase + + ilCode.Trim() |> stripComments |> unifyingAssemblyNames + + + let private generateIlFile dllFilePath ildasmArgs = let ilFilePath = Path.ChangeExtension(dllFilePath, ".il") - let mutable errorMsgOpt = None - let mutable actualIL = String.Empty let ildasmPath = config.ILDASM - let ildasmFullArgs = [ yield dllFilePath; yield sprintf "-out=%s" ilFilePath; yield! ildasmArgs ] + let ildasmFullArgs = [ dllFilePath; $"-out=%s{ilFilePath}"; yield! ildasmArgs ] let stdErr, exitCode = let ildasmCommandPath = Path.ChangeExtension(dllFilePath, ".ildasmCommandPath") @@ -45,100 +78,55 @@ module ILChecker = exec ildasmPath ildasmFullArgs if exitCode <> 0 then - failwith (sprintf "ILASM Expected exit code \"0\", got \"%d\"\nSTDERR: %s" exitCode stdErr) + failwith $"ILASM Expected exit code \"0\", got \"%d{exitCode}\"\nSTDERR: %s{stdErr}" if not (String.IsNullOrWhiteSpace stdErr) then - failwith (sprintf "ILASM Stderr is not empty:\n %s" stdErr) + failwith $"ILASM Stderr is not empty:\n %s{stdErr}" - let blockComments = @"/\*(.*?)\*/" - let lineComments = @"//(.*?)\r?\n" - let lineCommentsEof = @"//(.*?)$" - let strings = @"""((\\[^\n]|[^""\n])*)""" - let verbatimStrings = @"@(""[^""]*"")+" - let stripComments (text:string) = - System.Text.RegularExpressions.Regex.Replace(text, - blockComments + "|" + lineComments + "|" + lineCommentsEof + "|" + strings + "|" + verbatimStrings, - (fun me -> - if (me.Value.StartsWith("/*") || me.Value.StartsWith("//")) then - if me.Value.StartsWith("//") then Environment.NewLine else String.Empty - else - me.Value), System.Text.RegularExpressions.RegexOptions.Singleline) - |> filterSpecialComment + ilFilePath - let unifyRuntimeAssemblyName ilCode = - let pass1 = - Regex.Replace( - ilCode, - "\[System\.Runtime\]|\[System\.Console\]|\[System\.Runtime\.Extensions\]|\[mscorlib\]|\[System\.Memory\]","[runtime]", - RegexOptions.Singleline) - let pass2 = - Regex.Replace( - pass1, - "(\.assembly extern (System\.Runtime|System\.Console|System\.Runtime\.Extensions|mscorlib|System\.Memory)){1}([^\}]*)\}",".assembly extern runtime { }", - RegexOptions.Singleline) - let pass3 = - Regex.Replace( - pass2, - "(\.assembly extern (FSharp.Core)){1}([^\}]*)\}",".assembly extern FSharp.Core { }", - RegexOptions.Singleline) - pass3 - - let unifyImageBase ilCode = - Regex.Replace( - ilCode, - "\.imagebase\s*0x\d*",".imagebase {value}", - RegexOptions.Singleline) - - let unifyIlText (text:string) = - let unifyingAssemblyNames (text:string)= - let asmName = Path.GetFileNameWithoutExtension(dllFilePath) - text.Replace(asmName, "assembly") - |> unifyRuntimeAssemblyName - |> unifyImageBase - - text.Trim() |> stripComments |> unifyingAssemblyNames - - let raw = File.ReadAllText(ilFilePath) - let unifiedInputText = raw |> unifyIlText + let private generateIL (dllFilePath: string) = + let assemblyName = Some (Path.GetFileNameWithoutExtension dllFilePath) + generateIlFile dllFilePath >> File.ReadAllText >> normalizeILText assemblyName - expectedIL - |> List.map (fun (ilCode: string) -> ilCode.Trim()) - |> List.iter (fun (ilCode: string) -> - let expectedLines = - (ilCode |> unifyIlText).Split('\n') + let private compareIL assemblyName (actualIL: string) expectedIL = + + let mutable errorMsgOpt = None + + let prepareLines (s: string) = + s.Split('\n') |> Array.map(fun e -> e.Trim('\r')) - |> Array.skipWhile(fun s -> String.IsNullOrWhiteSpace(s)) + |> Array.skipWhile(String.IsNullOrWhiteSpace) |> Array.rev - |> Array.skipWhile(fun s -> String.IsNullOrWhiteSpace(s)) + |> Array.skipWhile(String.IsNullOrWhiteSpace) |> Array.rev + expectedIL + |> List.map (fun (ilCode: string) -> ilCode.Trim()) + |> List.iter (fun (ilCode: string) -> + let expectedLines = ilCode |> normalizeILText (Some assemblyName) |> prepareLines + if expectedLines.Length = 0 then errorMsgOpt <- Some("ExpectedLines length invalid: 0") else let startIndex = - let index = unifiedInputText.IndexOf(expectedLines[0].Trim()) + let index = actualIL.IndexOf(expectedLines[0].Trim()) if index > 0 then index else 0 - let actualLines = - unifiedInputText.Substring(startIndex).Split('\n') - |> Array.map(fun e -> e.Trim('\r')) - |> Array.skipWhile(fun s -> String.IsNullOrWhiteSpace(s)) - |> Array.rev - |> Array.skipWhile(fun s -> String.IsNullOrWhiteSpace(s)) - |> Array.rev + let actualLines = actualIL.Substring(startIndex) |> prepareLines let errors = ResizeArray() if actualLines.Length < expectedLines.Length then - let msg = sprintf "\nExpected at least %d lines but found only %d\n" expectedLines.Length actualLines.Length + let msg = $"\nExpected at least %d{expectedLines.Length} lines but found only %d{actualLines.Length}\n" errorMsgOpt <- Some(msg + "\nExpected:\n" + ilCode + "\n") else for i = 0 to expectedLines.Length - 1 do let expected = expectedLines[i].Trim() let actual = actualLines[i].Trim() if expected <> actual then - errors.Add(sprintf "\n==\nName: '%s'\n\nExpected:\t %s\nActual:\t\t %s\n==" actualLines[0] expected actual) + errors.Add $"\n==\nName: '%s{actualLines[0]}'\n\nExpected:\t %s{expected}\nActual:\t\t %s{actual}\n==" if errors.Count > 0 then let msg = String.concat "\n" errors + "\n\n\Expected:\n" + ilCode + "\n" @@ -148,21 +136,22 @@ module ILChecker = if expectedIL.Length = 0 then errorMsgOpt <- Some ("No Expected IL") - actualIL <- unifiedInputText - match errorMsgOpt with - | Some(msg) -> errorMsgOpt <- Some(msg + "\n\n\nEntire actual:\n" + unifiedInputText) + | Some(msg) -> errorMsgOpt <- Some(msg + "\n\n\nEntire actual:\n" + actualIL) | _ -> () match errorMsgOpt with | Some(errorMsg) -> (false, errorMsg, actualIL) | _ -> (true, String.Empty, String.Empty) + let private checkILPrim ildasmArgs dllFilePath = + let actualIL = generateIL dllFilePath ildasmArgs + compareIL (Path.GetFileNameWithoutExtension dllFilePath) actualIL + let private checkILAux ildasmArgs dllFilePath expectedIL = let (success, errorMsg, _) = checkILPrim ildasmArgs dllFilePath expectedIL if not success then Assert.Fail(errorMsg) - else () // This doesn't work because the '/linenum' is being ignored by // the version of ILDASM we are using, which we acquire from a nuget package @@ -178,7 +167,20 @@ module ILChecker = let verifyILAndReturnActual (dllFilePath: string) (expectedIL: string) = checkILPrim [] dllFilePath [expectedIL] + let checkILNotPresent dllFilePath unexpectedIL = + let actualIL = generateIL dllFilePath [] + if unexpectedIL = [] then + Assert.Fail $"No unexpected IL given. This is actual IL: \n{actualIL}" + let errors = + unexpectedIL + |> Seq.map (normalizeILText None) + |> Seq.filter actualIL.Contains + |> Seq.map (sprintf "Found in actual IL: '%s'") + |> String.concat "\n" + if errors <> "" then + Assert.Fail $"{errors}\n\n\nEntire actual:\n{actualIL}" + let reassembleIL ilFilePath dllFilePath = let ilasmPath = config.ILASM - let errors, _ = exec ilasmPath ([ sprintf "%s /output=%s /dll" ilFilePath dllFilePath ]) + let errors, _ = exec ilasmPath [ $"%s{ilFilePath} /output=%s{dllFilePath} /dll" ] errors diff --git a/tests/fsharpqa/Source/CompilerOptions/fsc/help/help40.437.1033.bsl b/tests/fsharpqa/Source/CompilerOptions/fsc/help/help40.437.1033.bsl index 2e41efee8f2..885f49a5c64 100644 --- a/tests/fsharpqa/Source/CompilerOptions/fsc/help/help40.437.1033.bsl +++ b/tests/fsharpqa/Source/CompilerOptions/fsc/help/help40.437.1033.bsl @@ -95,6 +95,8 @@ Copyright (c) Microsoft Corporation. All Rights Reserved. names output by the compiler --crossoptimize[+|-] Enable or disable cross-module optimizations +--reflectionfree Disable implicit generation of + constructs using reflection - ERRORS AND WARNINGS - diff --git a/tests/fsharpqa/Source/CompilerOptions/fsi/exename/help40.437.1033.bsl b/tests/fsharpqa/Source/CompilerOptions/fsi/exename/help40.437.1033.bsl index d7ab69ca858..d916a6c2eba 100644 --- a/tests/fsharpqa/Source/CompilerOptions/fsi/exename/help40.437.1033.bsl +++ b/tests/fsharpqa/Source/CompilerOptions/fsi/exename/help40.437.1033.bsl @@ -38,6 +38,8 @@ Usage: fsharpi [script.fsx []] names output by the compiler --crossoptimize[+|-] Enable or disable cross-module optimizations +--reflectionfree Disable implicit generation of + constructs using reflection - ERRORS AND WARNINGS - diff --git a/tests/fsharpqa/Source/CompilerOptions/fsi/help/help40-nologo.437.1033.bsl b/tests/fsharpqa/Source/CompilerOptions/fsi/help/help40-nologo.437.1033.bsl index a7f1930e318..906df1bcee4 100644 --- a/tests/fsharpqa/Source/CompilerOptions/fsi/help/help40-nologo.437.1033.bsl +++ b/tests/fsharpqa/Source/CompilerOptions/fsi/help/help40-nologo.437.1033.bsl @@ -38,6 +38,8 @@ Usage: fsiAnyCpu [script.fsx []] names output by the compiler --crossoptimize[+|-] Enable or disable cross-module optimizations +--reflectionfree Disable implicit generation of + constructs using reflection - ERRORS AND WARNINGS - diff --git a/tests/fsharpqa/Source/CompilerOptions/fsi/help/help40.437.1033.bsl b/tests/fsharpqa/Source/CompilerOptions/fsi/help/help40.437.1033.bsl index 65a00be5c48..87541384a88 100644 --- a/tests/fsharpqa/Source/CompilerOptions/fsi/help/help40.437.1033.bsl +++ b/tests/fsharpqa/Source/CompilerOptions/fsi/help/help40.437.1033.bsl @@ -40,6 +40,8 @@ Usage: fsiAnyCpu [script.fsx []] names output by the compiler --crossoptimize[+|-] Enable or disable cross-module optimizations +--reflectionfree Disable implicit generation of + constructs using reflection - ERRORS AND WARNINGS -