Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C# collection expression support for F# lists & sets #17359

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c76c603
C# collection expression support for lists & sets
brianrourkeboll Jun 27, 2024
b2ef7a9
Add trailing newline
brianrourkeboll Jun 27, 2024
b4facf9
These should be `internal`
brianrourkeboll Jun 27, 2024
69410bc
Cleanup
brianrourkeboll Jun 27, 2024
1700130
Update FSharp.Core surface area
brianrourkeboll Jun 27, 2024
5699cb9
Update release notes
brianrourkeboll Jun 27, 2024
21b61a1
Meant to use that
brianrourkeboll Jun 27, 2024
29ab91e
Remove redundant attribute
brianrourkeboll Jun 27, 2024
d84ab1c
Add doc comments
brianrourkeboll Jun 27, 2024
4c3ab92
Missed that test name
brianrourkeboll Jun 27, 2024
7d114f8
Add runner JSON
brianrourkeboll Jun 28, 2024
94a8541
Replace ifdef with comment
brianrourkeboll Jun 28, 2024
d93e696
Add direct tests for non-generic List & Set types
brianrourkeboll Jun 28, 2024
a0de93d
Merge branch 'main' of https://github.com/dotnet/fsharp into csharp-c…
brianrourkeboll Jun 28, 2024
eb0e1a2
Link RFC PR
brianrourkeboll Jun 28, 2024
fca5827
Make test project work
brianrourkeboll Jun 28, 2024
af5779c
Merge branch 'main' of https://github.com/dotnet/fsharp into csharp-c…
brianrourkeboll Jun 28, 2024
6368265
Copy/paste bug
brianrourkeboll Jun 28, 2024
44e0dcd
Probably also not
brianrourkeboll Jun 29, 2024
e4a1f08
Not that either
brianrourkeboll Jun 29, 2024
b0e026e
Merge branch 'main' of https://github.com/dotnet/fsharp into csharp-c…
brianrourkeboll Jul 3, 2024
57d6ec6
Merge branch 'main' of https://github.com/dotnet/fsharp into csharp-c…
brianrourkeboll Jul 8, 2024
976f140
Add C# 12 language version case
brianrourkeboll Jul 8, 2024
2d0f88f
Add interop tests
brianrourkeboll Jul 8, 2024
e5e67c8
Remove separate C# test project
brianrourkeboll Jul 8, 2024
a55e7aa
Merge branch 'main' of https://github.com/dotnet/fsharp into csharp-c…
brianrourkeboll Jul 8, 2024
edc1db7
Remove refs to removed test project
brianrourkeboll Jul 8, 2024
8892229
Move release notes
brianrourkeboll Jul 8, 2024
9fcaf8a
Merge branch 'main' of https://github.com/dotnet/fsharp into csharp-c…
brianrourkeboll Jul 8, 2024
bf1a1b7
Update trimmed size
brianrourkeboll Jul 8, 2024
24d1df6
Need span
brianrourkeboll Jul 8, 2024
d358314
Update trimmed size
brianrourkeboll Jul 8, 2024
860ab08
Merge branch 'main' into csharp-collection-expression-support
brianrourkeboll Jul 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion buildtools/AssemblyCheck/SkipVerifyEmbeddedPdb.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ FSharp.Test.Utilities.dll
FSharp.Compiler.Private.Scripting.UnitTests.dll
FSharp.Compiler.Service.Tests.dll
FSharp.Core.UnitTests.dll
FSharpSuite.Tests.dll
FSharpSuite.Tests.dll
2 changes: 1 addition & 1 deletion docs/release-notes/.FSharp.Core/8.0.400.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@

### Breaking Changes

* Fixed argument exception throwing inconsistency - accessing an out-of-bounds collection index will now throw `ArgumentOutOfRangeException` instead of `ArgumentException` ([#17328](https://github.com/dotnet/fsharp/pull/17328))
* Fixed argument exception throwing inconsistency - accessing an out-of-bounds collection index will now throw `ArgumentOutOfRangeException` instead of `ArgumentException` ([#17328](https://github.com/dotnet/fsharp/pull/17328))
2 changes: 2 additions & 0 deletions docs/release-notes/.FSharp.Core/9.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### Added

* Enable C# collection expression support for F# lists & sets. ([Language suggestion #1355](https://github.com/fsharp/fslang-suggestions/issues/1355), [RFC FS-1145 (PR#776)](https://github.com/fsharp/fslang-design/pull/776), [PR #17359](https://github.com/dotnet/fsharp/pull/17359))

### Changed
* Change compiler default setting realsig+ when building assemblies ([Issue #17384](https://github.com/dotnet/fsharp/issues/17384), [PR #17378](https://github.com/dotnet/fsharp/pull/17385))
* Change compiler default setting for compressedMetadata ([Issue #17379](https://github.com/dotnet/fsharp/issues/17379), [PR #17383](https://github.com/dotnet/fsharp/pull/17383))
Expand Down
36 changes: 36 additions & 0 deletions src/FSharp.Core/prim-types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,9 @@ namespace Microsoft.FSharp.Core

open BasicInlinedOperations

// This exists solely so that it can be used in the CollectionBuilderAttribute on List<'T> in prim-types.fsi.
module internal TypeOfUtils =
let inline typeof<'T> = typeof<'T>

module TupleUtils =

Expand Down Expand Up @@ -4069,6 +4072,23 @@ namespace Microsoft.FSharp.Core

and 'T voption = ValueOption<'T>

// These attributes only exist in .NET 8 and up.
namespace System.Runtime.CompilerServices
open System
open Microsoft.FSharp.Core

[<Sealed>]
[<AttributeUsage(AttributeTargets.Class ||| AttributeTargets.Struct ||| AttributeTargets.Interface, Inherited = false)>]
type internal CollectionBuilderAttribute (builderType: Type, methodName: string) =
inherit Attribute ()
member _.BuilderType = builderType
member _.MethodName = methodName

[<Sealed>]
[<AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)>]
type internal ScopedRefAttribute () =
inherit Attribute ()

namespace Microsoft.FSharp.Collections

//-------------------------------------------------------------------------
Expand All @@ -4086,6 +4106,9 @@ namespace Microsoft.FSharp.Collections
open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicFunctions
open Microsoft.FSharp.Core.BasicInlinedOperations

#if NETSTANDARD2_1_OR_GREATER
[<System.Runtime.CompilerServices.CollectionBuilder(typeof<List>, "Create")>]
#endif
[<DefaultAugmentation(false)>]
[<DebuggerTypeProxyAttribute(typedefof<ListDebugView<_>>)>]
[<DebuggerDisplay("{DebugDisplay,nq}")>]
Expand All @@ -4111,6 +4134,19 @@ namespace Microsoft.FSharp.Collections

and 'T list = List<'T>

#if NETSTANDARD2_1_OR_GREATER
and [<CompilerMessage("This type is for compiler use and should not be used directly", 1204, IsHidden=true);
Sealed;
AbstractClass;
CompiledName("FSharpList")>] List =
[<CompilerMessage("This method is for compiler use and should not be used directly", 1204, IsHidden=true)>]
static member Create([<System.Runtime.CompilerServices.ScopedRef>] items: System.ReadOnlySpan<'T>) =
let mutable list : 'T list = []
for i = items.Length - 1 downto 0 do
list <- items[i] :: list
list
brianrourkeboll marked this conversation as resolved.
Show resolved Hide resolved
#endif

//-------------------------------------------------------------------------
// List (debug view)
//-------------------------------------------------------------------------
Expand Down
58 changes: 57 additions & 1 deletion src/FSharp.Core/prim-types.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,10 @@ namespace Microsoft.FSharp.Core
/// <category>ByRef and Pointer Types</category>
type outref<'T> = byref<'T, ByRefKinds.Out>

// This exists solely so that it can be used in the CollectionBuilderAttribute on List<'T> below.
module internal TypeOfUtils =
val inline typeof<'T>: Type

/// <summary>Language primitives associated with the F# language</summary>
///
/// <category index="9">Language Primitives</category>
Expand Down Expand Up @@ -2578,12 +2582,46 @@ namespace Microsoft.FSharp.Core
/// Represents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
| Error of ErrorValue:'TError

// These attributes only exist in .NET 8 and up.
namespace System.Runtime.CompilerServices
open System
open Microsoft.FSharp.Core

[<Sealed>]
[<AttributeUsage(AttributeTargets.Class ||| AttributeTargets.Struct ||| AttributeTargets.Interface, Inherited = false)>]
type internal CollectionBuilderAttribute =
inherit Attribute

/// <summary>Initialize the attribute to refer to the <paramref name="methodName"/> method on the <paramref name="builderType"/> type.</summary>
/// <param name="builderType">The type of the builder to use to construct the collection.</param>
/// <param name="methodName">The name of the method on the builder to use to construct the collection.</param>
/// <remarks>
/// <paramref name="methodName"/> must refer to a static method that accepts a single parameter of
/// type <see cref="T:System.ReadOnlySpan`1"/> and returns an instance of the collection being built containing
/// a copy of the data from that span. In future releases of .NET, additional patterns may be supported.
/// </remarks>
new: builderType: Type * methodName: string -> CollectionBuilderAttribute

/// <summary>Gets the type of the builder to use to construct the collection.</summary>
member BuilderType: Type

/// <summary>Gets the name of the method on the builder to use to construct the collection.</summary>
/// <remarks>This should match the metadata name of the target method. For example, this might be ".ctor" if targeting the type's constructor.</remarks>
member MethodName: string

[<Sealed>]
[<AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)>]
type internal ScopedRefAttribute =
inherit Attribute
new: unit -> ScopedRefAttribute

namespace Microsoft.FSharp.Collections

open System
open System.Collections
open System.Collections.Generic
open Microsoft.FSharp.Core
open Microsoft.FSharp.Core.TypeOfUtils

/// <summary>The type of immutable singly-linked lists.</summary>
///
Expand All @@ -2593,6 +2631,9 @@ namespace Microsoft.FSharp.Collections
/// </remarks>
///
/// <exclude />
#if NETSTANDARD2_1_OR_GREATER
[<System.Runtime.CompilerServices.CollectionBuilder(typeof<List>, "Create")>]
#endif
[<DefaultAugmentation(false)>]
[<StructuralEquality; StructuralComparison>]
[<CompiledName("FSharpList`1")>]
Expand Down Expand Up @@ -2646,7 +2687,7 @@ namespace Microsoft.FSharp.Collections
///
/// <returns>The list with head appended to the front of tail.</returns>
static member Cons: head: 'T * tail: 'T list -> 'T list

interface IEnumerable<'T>
interface IEnumerable
interface IReadOnlyCollection<'T>
Expand All @@ -2664,6 +2705,21 @@ namespace Microsoft.FSharp.Collections
/// </remarks>
and 'T list = List<'T>

#if NETSTANDARD2_1_OR_GREATER
/// <summary>Contains methods for compiler use related to lists.</summary>
and [<CompilerMessage("This type is for compiler use and should not be used directly", 1204, IsHidden=true);
Sealed;
AbstractClass;
CompiledName("FSharpList")>] List =
/// <summary>Creates a list with the specified items.</summary>
///
/// <param name="items">The items to store in the list.</param>
///
/// <returns>A list containing the specified items.</returns>
[<CompilerMessage("This method is for compiler use and should not be used directly", 1204, IsHidden=true)>]
static member Create: [<System.Runtime.CompilerServices.ScopedRef>] items: System.ReadOnlySpan<'T> -> 'T list
#endif

/// <summary>An abbreviation for the CLI type <see cref="T:System.Collections.Generic.List`1"/></summary>
type ResizeArray<'T> = System.Collections.Generic.List<'T>

Expand Down
19 changes: 19 additions & 0 deletions src/FSharp.Core/set.fs
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,9 @@ module internal SetTree =
let ofArray comparer l =
Array.fold (fun acc k -> add comparer k acc) empty l

#if NETSTANDARD2_1_OR_GREATER
[<System.Runtime.CompilerServices.CollectionBuilder(typeof<Set>, "Create")>]
#endif
[<Sealed>]
[<CompiledName("FSharpSet`1")>]
[<DebuggerTypeProxy(typedefof<SetDebugView<_>>)>]
Expand Down Expand Up @@ -1023,6 +1026,22 @@ type Set<[<EqualityConditionalOn>] 'T when 'T: comparison>(comparer: IComparer<'
.Append("; ... ]")
.ToString()

#if NETSTANDARD2_1_OR_GREATER
and [<CompilerMessage("This type is for compiler use and should not be used directly", 1204, IsHidden = true);
Sealed;
AbstractClass;
CompiledName("FSharpSet")>] Set =
[<CompilerMessage("This method is for compiler use and should not be used directly", 1204, IsHidden = true)>]
static member Create([<System.Runtime.CompilerServices.ScopedRef>] items: System.ReadOnlySpan<'T>) =
let comparer = LanguagePrimitives.FastGenericComparer<'T>
let mutable acc = SetTree.empty

for item in items do
acc <- SetTree.add comparer item acc

Set(comparer, acc)
#endif

and [<Sealed>] SetDebugView<'T when 'T: comparison>(v: Set<'T>) =

[<DebuggerBrowsable(DebuggerBrowsableState.RootHidden)>]
Expand Down
18 changes: 18 additions & 0 deletions src/FSharp.Core/set.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ open Microsoft.FSharp.Collections
/// <remarks>See the <see cref="T:Microsoft.FSharp.Collections.SetModule"/> module for further operations on sets.
///
/// All members of this class are thread-safe and may be used concurrently from multiple threads.</remarks>
#if NETSTANDARD2_1_OR_GREATER
[<System.Runtime.CompilerServices.CollectionBuilder(typeof<Set>, "Create")>]
#endif
[<Sealed>]
[<CompiledName("FSharpSet`1")>]
type Set<[<EqualityConditionalOn>] 'T when 'T: comparison> =
Expand Down Expand Up @@ -233,6 +236,21 @@ type Set<[<EqualityConditionalOn>] 'T when 'T: comparison> =
interface IReadOnlyCollection<'T>
override Equals: obj -> bool

#if NETSTANDARD2_1_OR_GREATER
/// <summary>Contains methods for compiler use related to sets.</summary>
and [<CompilerMessage("This type is for compiler use and should not be used directly", 1204, IsHidden = true);
Sealed;
AbstractClass;
CompiledName("FSharpSet")>] Set =
/// <summary>Creates a set with the specified items.</summary>
///
/// <param name="items">The items to store in the set.</param>
///
/// <returns>A set containing the specified items.</returns>
[<CompilerMessage("This method is for compiler use and should not be used directly", 1204, IsHidden = true)>]
static member Create: [<System.Runtime.CompilerServices.ScopedRef>] items: System.ReadOnlySpan<'T> -> Set<'T>
#endif

namespace Microsoft.FSharp.Collections

open System
Expand Down
4 changes: 2 additions & 2 deletions tests/AheadOfTime/Trimming/check.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function CheckTrim($root, $tfm, $outputfile, $expected_len) {
# error NETSDK1124: Trimming assemblies requires .NET Core 3.0 or higher.

# Check net7.0 trimmed assemblies
CheckTrim -root "SelfContained_Trimming_Test" -tfm "net8.0" -outputfile "FSharp.Core.dll" -expected_len 284672
CheckTrim -root "SelfContained_Trimming_Test" -tfm "net8.0" -outputfile "FSharp.Core.dll" -expected_len 285184

# Check net8.0 trimmed assemblies
CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net8.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 8818176
CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net8.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 8818688
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ Microsoft.FSharp.Collections.ComparisonIdentity: System.Collections.Generic.ICom
Microsoft.FSharp.Collections.ComparisonIdentity: System.Collections.Generic.IComparer`1[T] NonStructural$W[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean]], Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean]])
Microsoft.FSharp.Collections.ComparisonIdentity: System.Collections.Generic.IComparer`1[T] NonStructural[T]()
Microsoft.FSharp.Collections.ComparisonIdentity: System.Collections.Generic.IComparer`1[T] Structural[T]()
Microsoft.FSharp.Collections.FSharpList: Microsoft.FSharp.Collections.FSharpList`1[T] Create[T](System.ReadOnlySpan`1[T])
Microsoft.FSharp.Collections.FSharpList`1+Tags[T]: Int32 Cons
Microsoft.FSharp.Collections.FSharpList`1+Tags[T]: Int32 Empty
Microsoft.FSharp.Collections.FSharpList`1[T]: Boolean Equals(Microsoft.FSharp.Collections.FSharpList`1[T])
Expand Down Expand Up @@ -274,6 +275,7 @@ Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: System.String ToString()
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: TValue Item [TKey]
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: TValue get_Item(TKey)
Microsoft.FSharp.Collections.FSharpMap`2[TKey,TValue]: Void .ctor(System.Collections.Generic.IEnumerable`1[System.Tuple`2[TKey,TValue]])
Microsoft.FSharp.Collections.FSharpSet: Microsoft.FSharp.Collections.FSharpSet`1[T] Create[T](System.ReadOnlySpan`1[T])
Microsoft.FSharp.Collections.FSharpSet`1[T]: Boolean Contains(T)
Microsoft.FSharp.Collections.FSharpSet`1[T]: Boolean Equals(System.Object)
Microsoft.FSharp.Collections.FSharpSet`1[T]: Boolean IsEmpty
Expand Down
1 change: 1 addition & 0 deletions tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
<Compile Include="FSharp.Core\Microsoft.FSharp.Control\EventModule.fs" />
<Compile Include="FSharp.Core\Microsoft.FSharp.Reflection\FSharpReflection.fs" />
<Compile Include="FSharp.Core\Microsoft.FSharp.Quotations\FSharpQuotations.fs" />
<Compile Include="Interop\CSharpCollectionExpressions.fs" />
<Compile Include="StructTuples.fs" />
<Compile Include="SurfaceArea.fs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,16 @@ type ListType() =
Assert.AreEqual(lst.[-3..(-4)], ([]: int list))
Assert.AreEqual(lst.[-4..(-3)], ([]: int list))

#if NET8_0_OR_GREATER

#nowarn "1204" // FS1204: This type/method is for compiler use and should not be used directly.

/// Tests for methods on the static, non-generic List type.
module FSharpList =
[<Fact>]
let ``List.Create creates a list from a ReadOnlySpan`` () =
let expected = [1..10]
let span = ReadOnlySpan [|1..10|]
let actual = List.Create span
Assert.Equal<int list>(expected, actual)
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,17 @@ type SetType() =
Assert.AreEqual(sec.MaximumElement, 7)
Assert.AreEqual(Set.maxElement fir, 6)
Assert.AreEqual(Set.maxElement sec, 7)

#if NET8_0_OR_GREATER

#nowarn "1204" // FS1204: This type/method is for compiler use and should not be used directly.

/// Tests for methods on the static, non-generic Set type.
module FSharpSet =
[<Fact>]
let ``Set.Create creates a set from a ReadOnlySpan`` () =
let expected = set [1..10]
let span = ReadOnlySpan [|1..10|]
let actual = Set.Create span
Assert.Equal<Set<int>>(expected, actual)
#endif
Loading
Loading