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