diff --git a/Directory.Build.props b/Directory.Build.props index 08a1434cdc1..1c34ff37fc8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -74,6 +74,8 @@ Cannot live in eng/TargetFrameworks.props because it's imported before Arcade. --> $(NetCurrent) + + $(NetCurrent) + + + TargetFramework=netstandard2.1 + + + 10) --> $([System.Text.RegularExpressions.Regex]::Replace('$(FSharpNetCoreProductTargetFramework)', '^net(\d+)\.0$', '$1')) + + + net10.0 diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index c9bc8c203d3..999a26f683a 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -6833,7 +6833,7 @@ and GenSequenceExpr let cloref = IlxClosureRef(ilCloTypeRef, ilCloLambdas, ilCloAllFreeVars) let ilxCloSpec = - IlxClosureSpec.Create(cloref, GenGenericArgs m eenvouter.tyenv cloFreeTyvars, false) + IlxClosureSpec.Create(cloref, GenGenericArgs cenv m eenvouter.tyenv cloFreeTyvars, false) let formalClospec = IlxClosureSpec.Create(cloref, mkILFormalGenericArgs 0 ilCloGenericParams, false) @@ -7075,8 +7075,10 @@ and GenStaticDelegateClosureTypeDefs and GenGenericParams cenv eenv tps = tps |> DropErasedTypars |> List.map (GenGenericParam cenv eenv) -and GenGenericArgs m (tyenv: TypeReprEnv) tps = - tps |> DropErasedTypars |> List.map (fun c -> (mkILTyvarTy tyenv[c, m])) +and GenGenericArgs cenv m (tyenv: TypeReprEnv) tps = + tps + |> DropErasedTypars + |> List.map (fun tp -> GenType cenv m tyenv (mkTyparTy tp)) /// Generate a local type function contract class and implementation and GenClosureAsLocalTypeFunction cenv (cgbuf: CodeGenBuffer) eenv thisVars expr m = @@ -7476,7 +7478,7 @@ and GetIlxClosureInfo cenv m boxity isLocalTypeFunc canUseStaticField thisVars e /// Compute the contract if it is a local type function let ilCloGenericFormals = GenGenericParams cenv eenvinner cloFreeTyvars - let ilCloGenericActuals = GenGenericArgs m eenvouter.tyenv cloFreeTyvars + let ilCloGenericActuals = GenGenericArgs cenv m eenvouter.tyenv cloFreeTyvars let useStaticField = canUseStaticField && (ilCloAllFreeVars.Length = 0) @@ -7784,7 +7786,7 @@ and GenDelegateExpr cenv cgbuf eenvouter expr (TObjExprMethod(slotsig, _attribs, CountClosure() // Push the constructor for the delegee - let ctxtGenericArgsForDelegee = GenGenericArgs m eenvouter.tyenv cloFreeTyvars + let ctxtGenericArgsForDelegee = GenGenericArgs cenv m eenvouter.tyenv cloFreeTyvars if useStaticClosure then GenUnit cenv eenvouter m cgbuf diff --git a/src/FSharp.Core/FSharp.Core.fsproj b/src/FSharp.Core/FSharp.Core.fsproj index 565fab62a04..be193aa3bbb 100644 --- a/src/FSharp.Core/FSharp.Core.fsproj +++ b/src/FSharp.Core/FSharp.Core.fsproj @@ -5,7 +5,7 @@ Library netstandard2.0 - netstandard2.0;netstandard2.1 + netstandard2.0;netstandard2.1;$(FSharpCoreShippedNetTargetFramework) $(NoWarn);75 $(NoWarn);1204 true @@ -36,6 +36,11 @@ Debug;Release;Proto + + + + + $(OtherFlags) --realsig- diff --git a/src/FSharp.Core/FSharp.Core.nuspec b/src/FSharp.Core/FSharp.Core.nuspec index cc7e5316ac6..a32a54e5382 100644 --- a/src/FSharp.Core/FSharp.Core.nuspec +++ b/src/FSharp.Core/FSharp.Core.nuspec @@ -6,6 +6,7 @@ + @@ -22,5 +23,11 @@ + + + + + + diff --git a/src/FSharp.Core/Query.fs b/src/FSharp.Core/Query.fs index 715894dd34a..61e2d2906db 100644 --- a/src/FSharp.Core/Query.fs +++ b/src/FSharp.Core/Query.fs @@ -107,7 +107,7 @@ type QueryBuilder() = member _.Head (source: QuerySource<'T, 'Q>) = Enumerable.First source.Source - member _.Nth (source: QuerySource<'T, 'Q>, index) = + member _.Nth (source: QuerySource<'T, 'Q>, index: int) = Enumerable.ElementAt (source.Source, index) member _.Skip (source: QuerySource<'T, 'Q>, count) : QuerySource<'T, 'Q> = @@ -116,7 +116,7 @@ type QueryBuilder() = member _.SkipWhile (source: QuerySource<'T, 'Q>, predicate) : QuerySource<'T, 'Q> = QuerySource (Enumerable.SkipWhile (source.Source, Func<_, _>(predicate))) - member _.Take (source: QuerySource<'T, 'Q>, count) : QuerySource<'T, 'Q> = + member _.Take (source: QuerySource<'T, 'Q>, count: int) : QuerySource<'T, 'Q> = QuerySource (Enumerable.Take (source.Source, count)) member _.TakeWhile (source: QuerySource<'T, 'Q>, predicate) : QuerySource<'T, 'Q> = @@ -246,7 +246,7 @@ type QueryBuilder() = QuerySource (Enumerable.GroupJoin(outerSource.Source, innerSource.Source, Func<_, _>(outerKeySelector), Func<_, _>(innerKeySelector), Func<_, _, _>(resultSelector))) member _.LeftOuterJoin (outerSource: QuerySource<_, 'Q>, innerSource: QuerySource<_, 'Q>, outerKeySelector, innerKeySelector, resultSelector: _ -> seq<_> -> _) : QuerySource<_, 'Q> = - QuerySource (Enumerable.GroupJoin(outerSource.Source, innerSource.Source, Func<_, _>(outerKeySelector), Func<_, _>(innerKeySelector), Func<_, _, _>(fun x g -> resultSelector x (g.DefaultIfEmpty())))) + QuerySource (Enumerable.GroupJoin(outerSource.Source, innerSource.Source, Func<_, _>(outerKeySelector), Func<_, _>(innerKeySelector), Func<_, _, _>(fun x (g: seq<_>) -> resultSelector x (g.DefaultIfEmpty())))) member _.RunQueryAsValue (q: Quotations.Expr<'T>) : 'T = ForwardDeclarations.Query.Execute q @@ -475,8 +475,8 @@ module Query = MakeOrCallContainsOrElementAt FQ FE let MakeElementAt, CallElementAt = - let FQ = methodhandleof (fun (x, y) -> Queryable.ElementAt(x, y)) - let FE = methodhandleof (fun (x, y) -> Enumerable.ElementAt(x, y)) + let FQ = methodhandleof (fun (x, y) -> Queryable.ElementAt(x, (y: int))) + let FE = methodhandleof (fun (x, y) -> Enumerable.ElementAt(x, (y: int))) MakeOrCallContainsOrElementAt FQ FE let MakeOrCallMinByOrMaxBy FQ FE = @@ -886,8 +886,8 @@ module Query = let MakeTake = MakeSkipOrTake - (methodhandleof (fun (x, y) -> Queryable.Take (x, y))) - (methodhandleof (fun (x, y) -> Enumerable.Take (x, y))) + (methodhandleof (fun (x, y) -> Queryable.Take (x, (y: int)))) + (methodhandleof (fun (x, y) -> Enumerable.Take (x, (y: int)))) let MakeSkipWhile = GenMakeSkipWhileOrTakeWhile diff --git a/src/FSharp.Core/async.fs b/src/FSharp.Core/async.fs index e73dc4aa230..caff822709c 100644 --- a/src/FSharp.Core/async.fs +++ b/src/FSharp.Core/async.fs @@ -2254,7 +2254,7 @@ type Async = static member Await(task: Task) : Async = AwaitUnitTask true task -#if NETSTANDARD2_1 +#if NETSTANDARD2_1 || NET static member Await(task: ValueTask<'T>) : Async<'T> = if task.IsCompletedSuccessfully then CreateReturnAsync(task.GetAwaiter().GetResult()) diff --git a/src/FSharp.Core/async.fsi b/src/FSharp.Core/async.fsi index 2171f75164a..e0631dc4cad 100644 --- a/src/FSharp.Core/async.fsi +++ b/src/FSharp.Core/async.fsi @@ -913,7 +913,7 @@ namespace Microsoft.FSharp.Control /// static member Await: task: Task -> Async -#if NETSTANDARD2_1 +#if NETSTANDARD2_1 || NET /// Creates an asynchronous computation that will wait for the given ValueTask to complete and return /// its result. /// The ValueTask to await. @@ -1291,13 +1291,13 @@ namespace Microsoft.FSharp.Control /// with IsCompleted and GetResult() members.

///

Exceptions thrown by GetResult() are propagated directly.

///

Unlike the -#if NETSTANDARD2_1 +#if NETSTANDARD2_1 || NET /// and #endif /// overloads, an carrying multiple inner exceptions is not preserved: /// the first inner exception surfaces (standard GetResult() semantics).

///

This overload uses statically resolved type parameters (SRTP) so it can accept any task-like type. -#if NETSTANDARD2_1 +#if NETSTANDARD2_1 || NET /// The specific overloads for , , /// and #else @@ -1931,4 +1931,3 @@ namespace Microsoft.FSharp.Control /// [] val empty: Async - diff --git a/src/FSharp.Core/local.fs b/src/FSharp.Core/local.fs index 16654a06257..c365f9e5bdb 100644 --- a/src/FSharp.Core/local.fs +++ b/src/FSharp.Core/local.fs @@ -10,17 +10,17 @@ module internal DetailedExceptions = open Microsoft.FSharp.Core /// takes an argument, a formatting string, a param array to splice into the formatting string - let inline invalidArgFmt (arg:string) (format:string) paramArray = + let inline invalidArgFmt (arg:string) (format:string) (paramArray: obj[]) = let msg = String.Format (format, paramArray) raise (ArgumentException(msg, arg)) /// takes an argument, a formatting string, a param array to splice into the formatting string - let inline invalidArgOutOfRangeFmt (arg:string) (format:string) paramArray = + let inline invalidArgOutOfRangeFmt (arg:string) (format:string) (paramArray: obj[]) = let msg = String.Format (format, paramArray) raise (ArgumentOutOfRangeException(arg, msg)) /// takes a formatting string and a param array to splice into the formatting string - let inline invalidOpFmt (format:string) paramArray = + let inline invalidOpFmt (format:string) (paramArray: obj[]) = let msg = String.Format (format, paramArray) raise (InvalidOperationException(msg)) diff --git a/src/FSharp.Core/prim-types.fs b/src/FSharp.Core/prim-types.fs index 036ba49ce48..5ee0a4e2ff8 100644 --- a/src/FSharp.Core/prim-types.fs +++ b/src/FSharp.Core/prim-types.fs @@ -443,13 +443,14 @@ namespace System.Diagnostics.CodeAnalysis member this.DynamicallyAccessedMembersAttribute(memberTypes: DynamicallyAccessedMemberTypes) = this.MemberTypes <- memberTypes +#endif + namespace Microsoft.FSharp.Core open System open System.Collections open System.Collections.Generic open System.Globalization open System.Reflection - #endif [] type float<[] 'Measure> = float [] type float32<[] 'Measure> = float32 @@ -4096,6 +4097,7 @@ namespace Microsoft.FSharp.Core and 'T voption = ValueOption<'T> // These attributes only exist in .NET 8 and up. +#if !NET8_0_OR_GREATER namespace System.Runtime.CompilerServices open System open Microsoft.FSharp.Core @@ -4111,6 +4113,7 @@ namespace System.Runtime.CompilerServices [] type internal ScopedRefAttribute () = inherit Attribute () +#endif namespace Microsoft.FSharp.Collections @@ -4128,7 +4131,7 @@ namespace Microsoft.FSharp.Collections open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicFunctions open Microsoft.FSharp.Core.BasicInlinedOperations -#if NETSTANDARD2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET [, "Create")>] #endif [] @@ -4156,7 +4159,7 @@ namespace Microsoft.FSharp.Collections and 'T list = List<'T> -#if NETSTANDARD2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET and [] type SupportsWhenTEnum = class end +#if !NET5_0_OR_GREATER namespace System.Diagnostics.CodeAnalysis open System @@ -1047,6 +1048,8 @@ namespace System.Diagnostics.CodeAnalysis new: DynamicallyAccessedMemberTypes -> DynamicallyAccessedMembersAttribute member MemberTypes: DynamicallyAccessedMemberTypes +#endif + namespace Microsoft.FSharp.Core open System @@ -2606,6 +2609,7 @@ namespace Microsoft.FSharp.Core | Error of ErrorValue:'TError // These attributes only exist in .NET 8 and up. +#if !NET8_0_OR_GREATER namespace System.Runtime.CompilerServices open System open Microsoft.FSharp.Core @@ -2637,6 +2641,7 @@ namespace System.Runtime.CompilerServices type internal ScopedRefAttribute = inherit Attribute new: unit -> ScopedRefAttribute +#endif namespace Microsoft.FSharp.Collections @@ -2654,7 +2659,7 @@ namespace Microsoft.FSharp.Collections /// /// /// -#if NETSTANDARD2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET [, "Create")>] #endif [] @@ -2730,7 +2735,7 @@ namespace Microsoft.FSharp.Collections /// and 'T list = List<'T> -#if NETSTANDARD2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET ///

Contains methods for compiler use related to lists. and [ add comparer k acc) empty l -#if NETSTANDARD2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET [, "Create")>] #endif [] @@ -1097,7 +1097,7 @@ type Set<[] 'T when 'T: comparison>(comparer: IComparer<' .Append("; ... ]") .ToString() -#if NETSTANDARD2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET and [See the module for further operations on sets. /// /// All members of this class are thread-safe and may be used concurrently from multiple threads. -#if NETSTANDARD2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET [, "Create")>] #endif [] @@ -263,7 +263,7 @@ type Set<[] 'T when 'T: comparison> = interface System.Collections.IStructuralEquatable interface IReadOnlyCollection<'T> -#if NETSTANDARD2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NET /// Contains methods for compiler use related to sets. and [, body: 'T -> TaskCode<'TOverall, unit>) : TaskCode<'TOverall, unit> = ResumableCode.For(sequence, body) -#if NETSTANDARD2_1 +#if NETSTANDARD2_1 || NET member inline internal this.TryFinallyAsync (body: TaskCode<'TOverall, 'T>, compensation: unit -> ValueTask) : TaskCode<'TOverall, 'T> = @@ -788,13 +788,13 @@ module Task = let catch (task: Task<'T>) : Task> = task |> map Ok |> catchWith Error -#if NETSTANDARD2_1 +#if NETSTANDARD2_1 || NET [] let inline ofValueTask (valueTask: ValueTask<'T>) : Task<'T> = valueTask.AsTask() #endif -#if NETSTANDARD2_1 +#if NETSTANDARD2_1 || NET [] [] module ValueTask = diff --git a/src/FSharp.Core/tasks.fsi b/src/FSharp.Core/tasks.fsi index a4e6806d824..f948c5a7044 100644 --- a/src/FSharp.Core/tasks.fsi +++ b/src/FSharp.Core/tasks.fsi @@ -93,7 +93,7 @@ type TaskBuilderBase = member inline TryWith: body: TaskCode<'TOverall, 'T> * catch: (exn -> TaskCode<'TOverall, 'T>) -> TaskCode<'TOverall, 'T> -#if NETSTANDARD2_1 +#if NETSTANDARD2_1 || NET /// /// Specifies a unit of task code which binds to the resource implementing IAsyncDisposable and disposes it asynchronously /// @@ -582,7 +582,7 @@ module Task = [] val empty: Task -#if NETSTANDARD2_1 +#if NETSTANDARD2_1 || NET /// Converts a to a . /// /// The input value task. @@ -600,7 +600,7 @@ module Task = val inline ofValueTask: valueTask: ValueTask<'T> -> Task<'T> #endif -#if NETSTANDARD2_1 +#if NETSTANDARD2_1 || NET /// Contains camelCase module-level functions for computations. /// /// Async Programming diff --git a/src/Microsoft.FSharp.Compiler/Microsoft.FSharp.Compiler.fsproj b/src/Microsoft.FSharp.Compiler/Microsoft.FSharp.Compiler.fsproj index ec0704c0cf1..f31891bc587 100644 --- a/src/Microsoft.FSharp.Compiler/Microsoft.FSharp.Compiler.fsproj +++ b/src/Microsoft.FSharp.Compiler/Microsoft.FSharp.Compiler.fsproj @@ -45,7 +45,7 @@ TargetFrameworks=netstandard2.0 - TargetFrameworks=netstandard2.1;netstandard2.0 + TargetFrameworks=netstandard2.1;netstandard2.0;$(FSharpCoreShippedNetTargetFramework) TargetFrameworks=netstandard2.0 diff --git a/tests/AheadOfTime/NetPackage/VerifyNetPackage.fsx b/tests/AheadOfTime/NetPackage/VerifyNetPackage.fsx new file mode 100644 index 00000000000..9e04522daa3 --- /dev/null +++ b/tests/AheadOfTime/NetPackage/VerifyNetPackage.fsx @@ -0,0 +1,69 @@ +open System +open System.IO +open System.IO.Compression +open System.Text.RegularExpressions + +let fail message = + eprintfn "Package verification failed: %s" message + exit 1 + +let requireExactlyOne description items = + match Array.ofSeq items with + | [| item |] -> item + | items -> fail $"{description}: expected exactly one, found {items.Length}" + +let packagesRoot = + match fsi.CommandLineArgs with + | [| _; path |] -> Path.GetFullPath path + | _ -> fail "expected the artifacts/packages directory" + +let packagePath = + Directory.EnumerateFiles(packagesRoot, "FSharp.Core.*.nupkg", SearchOption.AllDirectories) + |> Seq.filter (fun path -> + DirectoryInfo(Path.GetDirectoryName path).Name = "Shipping" + && not (Path.GetFileName(path).EndsWith(".symbols.nupkg", StringComparison.Ordinal))) + |> requireExactlyOne "FSharp.Core package" + +let package = ZipFile.OpenRead packagePath + +let entries = + package.Entries + |> Seq.map (fun entry -> entry.FullName, entry) + |> Map.ofSeq + +let netTfm = + entries.Keys + |> Seq.choose (fun path -> + let matched = Regex.Match(path, "^lib/(net\\d+\\.0)/FSharp\\.Core\\.dll$") + if matched.Success then Some matched.Groups[1].Value else None) + |> Seq.distinct + |> requireExactlyOne "versioned net target" + +let requireNonEmpty path = + match entries.TryFind path with + | Some entry when entry.Length > 0L -> () + | Some _ -> fail $"{path} is empty" + | None -> fail $"{path} is missing" + +for tfm in [ "netstandard2.0"; "netstandard2.1"; netTfm ] do + requireNonEmpty $"lib/{tfm}/FSharp.Core.dll" + requireNonEmpty $"lib/{tfm}/FSharp.Core.xml" + +let satellites tfm = + entries + |> Seq.choose (fun (KeyValue(path, entry)) -> + let matched = Regex.Match(path, $"^lib/{Regex.Escape tfm}/([^/]+)/FSharp\\.Core\\.resources\\.dll$") + if matched.Success then Some(matched.Groups[1].Value, entry) else None) + |> Map.ofSeq + +let referenceSatellites = satellites "netstandard2.1" +let netSatellites = satellites netTfm + +if referenceSatellites.IsEmpty || Set.ofSeq referenceSatellites.Keys <> Set.ofSeq netSatellites.Keys then + fail $"{netTfm} satellite assemblies do not match netstandard2.1" + +netSatellites.Values +|> Seq.iter (fun entry -> if entry.Length = 0L then fail $"{entry.FullName} is empty") + +package.Dispose() +printfn "Verified %s with %s assets." (Path.GetFileName packagePath) netTfm diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj index 7130f40e785..e043d8554ad 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj @@ -213,6 +213,12 @@ TargetFramework=netstandard2.0 + + + TargetFramework=netstandard2.1 + diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj b/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj index faf41f04322..6850edff337 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj @@ -98,8 +98,18 @@ - + + TargetFramework=$(FSharpCoreShippedNetTargetFramework) + + + + + diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/TaskModuleFunctions.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/TaskModuleFunctions.fs index b3b40724a81..6791e0ffc07 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/TaskModuleFunctions.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/TaskModuleFunctions.fs @@ -302,13 +302,14 @@ module TaskModuleFunctionsTests = Assert.Equal((), t.Result) -#if NETSTANDARD2_1 +#if NETSTANDARD2_1 || NET [] let ``Task.ofValueTask converts ValueTask`` () = let vt = ValueTask(42) let t = Task.ofValueTask vt Assert.Equal(42, t.Result) + [] let ``Task.ofValueTask converts faulted ValueTask`` () = let vt = ValueTask(Task.FromException(Exception "boom")) let t = Task.ofValueTask vt diff --git a/tests/FSharp.Core.UnitTests/SurfaceArea.fs b/tests/FSharp.Core.UnitTests/SurfaceArea.fs index 8adf6e5f512..c52b1fba19f 100644 --- a/tests/FSharp.Core.UnitTests/SurfaceArea.fs +++ b/tests/FSharp.Core.UnitTests/SurfaceArea.fs @@ -4,6 +4,7 @@ namespace FSharp.Core.UnitTests.Portable.SurfaceArea open Xunit open System.IO +open System.Reflection open FSharp.Test type SurfaceAreaTest() = @@ -18,13 +19,10 @@ type SurfaceAreaTest() = // Linux/macOS: // export TEST_UPDATE_BSL=1 [] - member this.VerifySurfaceAreaFSharpCore () : unit = + member _.VerifySurfaceAreaFSharpCore () : unit = let platform = // We are testing the surface area of the FSharp.Core assembly. -// NETCOREAPP builds with netstandard2.1 -// Net472 builds with netstandard1.0 -// #if NETCOREAPP "netstandard21" #else @@ -38,4 +36,27 @@ type SurfaceAreaTest() = #endif let assembly = typeof.Assembly let baseline = Path.Combine(__SOURCE_DIRECTORY__, $"FSharp.Core.SurfaceArea.{platform}.{flavor}.bsl") +#if NETCOREAPP + SurfaceArea.verifyIgnoringAssemblyReferences assembly baseline +#else + SurfaceArea.verify assembly baseline +#endif + +#if NETCOREAPP + [] + member _.VerifyNetStandard21SurfaceAreaFSharpCore () : unit = +#if DEBUG + let configuration = "Debug" + let flavor = "debug" +#else + let configuration = "Release" + let flavor = "release" +#endif + let assemblyPath = + Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "artifacts", "bin", "FSharp.Core", configuration, "netstandard2.1", "FSharp.Core.dll") + |> Path.GetFullPath + + let assembly = Assembly.LoadFile assemblyPath + let baseline = Path.Combine(__SOURCE_DIRECTORY__, $"FSharp.Core.SurfaceArea.netstandard21.{flavor}.bsl") SurfaceArea.verify assembly baseline +#endif diff --git a/tests/FSharp.Test.Utilities/SurfaceArea.fs b/tests/FSharp.Test.Utilities/SurfaceArea.fs index 7e60103175b..8b6e6c90748 100644 --- a/tests/FSharp.Test.Utilities/SurfaceArea.fs +++ b/tests/FSharp.Test.Utilities/SurfaceArea.fs @@ -42,19 +42,24 @@ module FSharp.Test.SurfaceArea |] assembly, actual - // verify public surface area matches expected, handles baseline update when TEST_UPDATE_BSL is set - let verify assembly baselinePath : unit = + let private verifyWith updateBaseline lineFilter assembly baselinePath : unit = let normalize (s:string) = Regex.Replace(s, "(\\r\\n|\\n|\\r)+", Environment.NewLine).Trim() let asm, actualNotNormalized = getSurfaceAreaForAssembly (assembly) - let actual = - actualNotNormalized + + let render lines = + lines |> Seq.map normalize |> Seq.filter (String.IsNullOrWhiteSpace >> not) + |> Seq.filter lineFilter |> Seq.sort |> String.concat Environment.NewLine + let actual = render actualNotNormalized + let compare fileContent produced = - match Assert.shouldBeSameMultilineStringSets (normalize fileContent) produced with + let expected = Regex.Split(fileContent, "\\r\\n|\\n|\\r") |> render + + match Assert.shouldBeSameMultilineStringSets expected produced with | None -> None | Some diff -> Some $"""Assembly: %A{asm} @@ -64,4 +69,16 @@ module FSharp.Test.SurfaceArea {diff}""" - checkBaselineWith compare actual baselinePath + if updateBaseline then + checkBaselineWith compare actual baselinePath + else + match compare (File.ReadAllText baselinePath) actual with + | None -> () + | Some diff -> failwith diff + + // verify public surface area matches expected, handles baseline update when TEST_UPDATE_BSL is set + let verify assembly baselinePath : unit = + verifyWith true (fun _ -> true) assembly baselinePath + + let verifyIgnoringAssemblyReferences assembly baselinePath : unit = + verifyWith false (fun line -> not (line.StartsWith("! AssemblyReference:", StringComparison.Ordinal))) assembly baselinePath diff --git a/tests/ILVerify/ilverify.ps1 b/tests/ILVerify/ilverify.ps1 index c870bbcf3d5..70e331b06c3 100644 --- a/tests/ILVerify/ilverify.ps1 +++ b/tests/ILVerify/ilverify.ps1 @@ -55,12 +55,13 @@ $env:PublishWindowsPdb = "false" [string] $default_tfm = "netstandard2.0" # Read product TFM from centralized source of truth via MSBuild [string] $product_tfm = (& (Join-Path $repo_path "eng/common/dotnet.ps1") msbuild (Join-Path $repo_path "eng/TargetFrameworks.props") --getProperty:FSharpNetCoreProductTargetFramework).Trim() +[string] $fsharp_core_net_tfm = (& (Join-Path $repo_path "eng/common/dotnet.ps1") msbuild (Join-Path $repo_path "eng/TargetFrameworks.props") --getProperty:FSharpCoreShippedNetTargetFramework).Trim() [string] $artifacts_bin_path = Join-Path (Join-Path $repo_path "artifacts") "bin" # List projects to verify, with TFMs $projects = @{ - "FSharp.Core" = @($default_tfm, "netstandard2.1") + "FSharp.Core" = @($default_tfm, "netstandard2.1", $fsharp_core_net_tfm) "FSharp.Compiler.Service" = @($default_tfm, $product_tfm) } diff --git a/tests/ILVerify/ilverify_FSharp.Core_Debug_netcoreapp.bsl b/tests/ILVerify/ilverify_FSharp.Core_Debug_netcoreapp.bsl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/ILVerify/ilverify_FSharp.Core_Release_netcoreapp.bsl b/tests/ILVerify/ilverify_FSharp.Core_Release_netcoreapp.bsl new file mode 100644 index 00000000000..e69de29bb2d