From 54a19976c4529286b360dace622060b0ce5317aa Mon Sep 17 00:00:00 2001 From: Vladimir Shchur Date: Wed, 26 Jun 2024 18:11:13 +0300 Subject: [PATCH 1/4] FS-1135 implementation - random functions for collections (#17277) * Random functions: old version rebased * Random functions: Rename functions according to the RFC * Random functions: More naming refactoring and documentation * Random functions: More documentation coments * Random functions: Added randomShuffleInPlace functions and docs * Random functions: refactoring * Added null checks * Added *by functions * Random functions: Array random functions tests * Added tests for lists and seqs * Random functions: addded *By tests to arrays and sequences * Random functions: addded *By tests to lists * Random functions: try fix CI * Random functions: review fixes * Try fix CI * Changed thread local implementation to thread static for performance per review * PR review fix * PR fix * PR review fix * Reverted HashSet constructor improvement since not netstandard2.0 compatible * Fix formatting * Fixed nan case for randomizer function * PR review fixes * PR review changes * Fixed input length check logic for sample --------- Co-authored-by: Petr --- docs/release-notes/.FSharp.Core/8.0.400.md | 2 + src/FSharp.Core/FSharp.Core.fsproj | 6 + src/FSharp.Core/Random.fs | 25 + src/FSharp.Core/Random.fsi | 7 + src/FSharp.Core/array.fs | 205 +++++++++ src/FSharp.Core/array.fsi | 322 +++++++++++++ src/FSharp.Core/list.fs | 171 +++++++ src/FSharp.Core/list.fsi | 255 +++++++++++ src/FSharp.Core/local.fs | 44 +- src/FSharp.Core/local.fsi | 9 + src/FSharp.Core/seq.fs | 184 ++++++++ src/FSharp.Core/seq.fsi | 286 ++++++++++++ ...Core.SurfaceArea.netstandard20.release.bsl | 39 ++ ...Core.SurfaceArea.netstandard21.release.bsl | 39 ++ .../ArrayModule.fs | 433 ++++++++++++++++++ .../ListModule.fs | 304 +++++++++++- .../Microsoft.FSharp.Collections/SeqModule.fs | 332 ++++++++++++++ 17 files changed, 2660 insertions(+), 3 deletions(-) create mode 100644 src/FSharp.Core/Random.fs create mode 100644 src/FSharp.Core/Random.fsi diff --git a/docs/release-notes/.FSharp.Core/8.0.400.md b/docs/release-notes/.FSharp.Core/8.0.400.md index 9c01378c5ef..3f9a780974b 100644 --- a/docs/release-notes/.FSharp.Core/8.0.400.md +++ b/docs/release-notes/.FSharp.Core/8.0.400.md @@ -2,6 +2,8 @@ ### Added +* `Random functions for collections` ([RFC #1135](https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1135-random-functions-for-collections.md), [PR #17277](https://github.com/dotnet/fsharp/pull/17277)) + ### Changed * Cache delegate in query extensions. ([PR #17130](https://github.com/dotnet/fsharp/pull/17130)) diff --git a/src/FSharp.Core/FSharp.Core.fsproj b/src/FSharp.Core/FSharp.Core.fsproj index dbfbf0006e6..32aeb2fc498 100644 --- a/src/FSharp.Core/FSharp.Core.fsproj +++ b/src/FSharp.Core/FSharp.Core.fsproj @@ -91,6 +91,12 @@ Primitives/prim-types.fs + + Random/Random.fsi + + + Random/Random.fs + Collections/local.fsi diff --git a/src/FSharp.Core/Random.fs b/src/FSharp.Core/Random.fs new file mode 100644 index 00000000000..72c0c96c49b --- /dev/null +++ b/src/FSharp.Core/Random.fs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Microsoft.FSharp.Core + +open System +open System.Runtime.CompilerServices +open System.Threading + +[] +type internal ThreadSafeRandom() = + + [] + [] + static val mutable private random: Random + + [] + static member private Create() = + ThreadSafeRandom.random <- Random() + ThreadSafeRandom.random + + // Don't pass the returned Random object between threads + static member Shared = + match ThreadSafeRandom.random with + | null -> ThreadSafeRandom.Create() + | random -> random diff --git a/src/FSharp.Core/Random.fsi b/src/FSharp.Core/Random.fsi new file mode 100644 index 00000000000..e65feb415a0 --- /dev/null +++ b/src/FSharp.Core/Random.fsi @@ -0,0 +1,7 @@ +namespace Microsoft.FSharp.Core + +open System + +[] +type internal ThreadSafeRandom = + static member Shared: Random diff --git a/src/FSharp.Core/array.fs b/src/FSharp.Core/array.fs index 344d7e3e7cf..77153012a5e 100644 --- a/src/FSharp.Core/array.fs +++ b/src/FSharp.Core/array.fs @@ -1936,6 +1936,211 @@ module Array = result + [] + let randomShuffleWith (random: Random) (source: 'T array) : 'T array = + checkNonNull "random" random + checkNonNull "source" source + + let result = copy source + + Microsoft.FSharp.Primitives.Basics.Random.shuffleArrayInPlaceWith random result + + result + + [] + let randomShuffleBy (randomizer: unit -> float) (source: 'T array) : 'T array = + checkNonNull "source" source + + let result = copy source + + Microsoft.FSharp.Primitives.Basics.Random.shuffleArrayInPlaceBy randomizer result + + result + + [] + let randomShuffle (source: 'T array) : 'T array = + randomShuffleWith ThreadSafeRandom.Shared source + + [] + let randomShuffleInPlaceWith (random: Random) (source: 'T array) = + checkNonNull "random" random + checkNonNull "source" source + + Microsoft.FSharp.Primitives.Basics.Random.shuffleArrayInPlaceWith random source + + [] + let randomShuffleInPlaceBy (randomizer: unit -> float) (source: 'T array) = + checkNonNull "source" source + + Microsoft.FSharp.Primitives.Basics.Random.shuffleArrayInPlaceBy randomizer source + + [] + let randomShuffleInPlace (source: 'T array) = + randomShuffleInPlaceWith ThreadSafeRandom.Shared source + + [] + let randomChoiceWith (random: Random) (source: 'T array) : 'T = + checkNonNull "random" random + checkNonNull "source" source + + let inputLength = source.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputArrayEmptyString + + let i = random.Next(0, inputLength) + source[i] + + [] + let randomChoiceBy (randomizer: unit -> float) (source: 'T array) : 'T = + checkNonNull "source" source + + let inputLength = source.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputArrayEmptyString + + let i = Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength + source[i] + + [] + let randomChoice (source: 'T array) : 'T = + randomChoiceWith ThreadSafeRandom.Shared source + + [] + let randomChoicesWith (random: Random) (count: int) (source: 'T array) : 'T array = + checkNonNull "random" random + checkNonNull "source" source + + if count < 0 then + invalidArgInputMustBeNonNegative "count" count + + let inputLength = source.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputArrayEmptyString + + let result = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked count + + for i = 0 to count - 1 do + let j = random.Next(0, inputLength) + result[i] <- source[j] + + result + + [] + let randomChoicesBy (randomizer: unit -> float) (count: int) (source: 'T array) : 'T array = + checkNonNull "source" source + + if count < 0 then + invalidArgInputMustBeNonNegative "count" count + + let inputLength = source.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputArrayEmptyString + + let result = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked count + + for i = 0 to count - 1 do + let j = Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength + result[i] <- source[j] + + result + + [] + let randomChoices (count: int) (source: 'T array) : 'T array = + randomChoicesWith ThreadSafeRandom.Shared count source + + [] + let randomSampleWith (random: Random) (count: int) (source: 'T array) : 'T array = + checkNonNull "random" random + checkNonNull "source" source + + if count < 0 then + invalidArgInputMustBeNonNegative "count" count + + let inputLength = source.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputArrayEmptyString + + if count > inputLength then + invalidArg "count" (SR.GetString(SR.notEnoughElements)) + + let result = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked count + + let setSize = + Microsoft.FSharp.Primitives.Basics.Random.getMaxSetSizeForSampling count + + if inputLength <= setSize then + let pool = copy source + + for i = 0 to count - 1 do + let j = random.Next(0, inputLength - i) + result[i] <- pool[j] + pool[j] <- pool[inputLength - i - 1] + else + let selected = HashSet() + + for i = 0 to count - 1 do + let mutable j = random.Next(0, inputLength) + + while not (selected.Add j) do + j <- random.Next(0, inputLength) + + result[i] <- source[j] + + result + + [] + let randomSampleBy (randomizer: unit -> float) (count: int) (source: 'T array) : 'T array = + checkNonNull "source" source + + if count < 0 then + invalidArgInputMustBeNonNegative "count" count + + let inputLength = source.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputArrayEmptyString + + if count > inputLength then + invalidArg "count" (SR.GetString(SR.notEnoughElements)) + + let result = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked count + + // algorithm taken from https://github.com/python/cpython/blob/69b3e8ea569faabccd74036e3d0e5ec7c0c62a20/Lib/random.py#L363-L456 + let setSize = + Microsoft.FSharp.Primitives.Basics.Random.getMaxSetSizeForSampling count + + if inputLength <= setSize then + let pool = copy source + + for i = 0 to count - 1 do + let j = + Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 (inputLength - i) + + result[i] <- pool[j] + pool[j] <- pool[inputLength - i - 1] + else + let selected = HashSet() + + for i = 0 to count - 1 do + let mutable j = + Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength + + while not (selected.Add j) do + j <- Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength + + result[i] <- source[j] + + result + + [] + let randomSample (count: int) (source: 'T array) : 'T array = + randomSampleWith ThreadSafeRandom.Shared count source + module Parallel = open System.Threading open System.Threading.Tasks diff --git a/src/FSharp.Core/array.fsi b/src/FSharp.Core/array.fsi index 8a5d51f0c9e..8947a2a8d07 100644 --- a/src/FSharp.Core/array.fsi +++ b/src/FSharp.Core/array.fsi @@ -3095,6 +3095,328 @@ module Array = [] val insertManyAt: index: int -> values: seq<'T> -> source: 'T array -> 'T array + /// Return a new array shuffled in a random order. + /// + /// The input array. + /// + /// The result array. + /// + /// Thrown when the input array is null. + /// + /// + /// + /// let inputs = [| 0; 1; 2; 3; 4 |] + /// + /// inputs |> Array.randomShuffle + /// + /// Can evaluate to [| 0; 2; 4; 3; 1 |]. + /// + [] + val randomShuffle: source: 'T array -> 'T array + + /// Return a new array shuffled in a random order with the specified Random instance. + /// + /// The Random instance. + /// The input array. + /// + /// The result array. + /// + /// Thrown when the input array is null. + /// Thrown when the random argument is null. + /// + /// + /// + /// let inputs = [| 0; 1; 2; 3; 4 |] + /// + /// inputs |> Array.randomShuffleWith Random.Shared + /// + /// Can evaluate to [| 0; 2; 4; 3; 1 |]. + /// + [] + val randomShuffleWith: random: Random -> source: 'T array -> 'T array + + /// Return a new array shuffled in a random order using the specified randomizer function. + /// + /// The randomizer function, must return a float number from [0.0..1.0) range. + /// The input array. + /// + /// The result array. + /// + /// Thrown when the input array is null. + /// Thrown when the randomizer function returns a value outside the range [0, 1). + /// + /// + /// + /// let inputs = [| 0; 1; 2; 3; 4 |] + /// + /// inputs |> Array.randomShuffleBy Random.Shared.NextDouble + /// + /// Can evaluate to [| 0; 2; 4; 3; 1 |]. + /// + [] + val randomShuffleBy: randomizer: (unit -> float) -> source: 'T array -> 'T array + + /// Sorts input array in a random order by mutating the array in-place. + /// + /// The input array. + /// + /// Thrown when the input array is null. + /// + /// + /// + /// let inputs = [| 0; 1; 2; 3; 4 |] + /// + /// inputs |> Array.randomShuffleInPlace + /// + /// After evaluation array can contain [| 0; 2; 4; 3; 1 |]. + /// + [] + val randomShuffleInPlace: source: 'T array -> unit + + /// Sorts input array in a random order with the specified Random instance by mutating the array in-place. + /// + /// The input array. + /// The Random instance. + /// + /// Thrown when the input array is null. + /// + /// + /// + /// let inputs = [| 0; 1; 2; 3; 4 |] + /// + /// inputs |> Array.randomShuffleInPlaceWith Random.Shared + /// + /// After evaluation array can contain [| 0; 2; 4; 3; 1 |]. + /// + [] + val randomShuffleInPlaceWith: random: Random -> source: 'T array -> unit + + /// Sorts input array in a random order using the specified randomizer function by mutating the array in-place. + /// + /// The randomizer function, must return a float number from [0.0..1.0) range. + /// The input array. + /// + /// Thrown when the input array is null. + /// Thrown when the randomizer function returns a value outside the range [0, 1). + /// + /// + /// + /// let inputs = [| 0; 1; 2; 3; 4 |] + /// + /// inputs |> Array.randomShuffleInPlaceBy Random.Shared.NextDouble + /// + /// After evaluation array can contain [| 0; 2; 4; 3; 1 |]. + /// + [] + val randomShuffleInPlaceBy: randomizer: (unit -> float) -> source: 'T array -> unit + + /// Returns a random element from the given array. + /// + /// The input array. + /// + /// A randomly selected element from the input array. + /// + /// Thrown when the input array is null. + /// Thrown when the input array is empty. + /// + /// + /// + /// let inputs = [| 0; 1; 2; 3; 4 |] + /// + /// inputs |> Array.randomChoice + /// + /// Can evaluate to 3. + /// + [] + val randomChoice: source: 'T array -> 'T + + /// Returns a random element from the given array with the specified Random instance. + /// + /// The Random instance. + /// The input array. + /// + /// A randomly selected element from the input array. + /// + /// Thrown when the input array is null. + /// Thrown when the random argument is null. + /// Thrown when the input array is empty. + /// + /// + /// + /// let inputs = [| 0; 1; 2; 3; 4 |] + /// + /// inputs |> Array.randomChoiceWith Random.Shared + /// + /// Can evaluate to 3. + /// + [] + val randomChoiceWith: random: Random -> source: 'T array -> 'T + + /// Returns a random element from the given array using the specified randomizer function. + /// + /// The randomizer function, must return a float number from [0.0..1.0) range. + /// The input array. + /// + /// A randomly selected element from the input array. + /// + /// Thrown when the input array is null. + /// Thrown when the input array is empty. + /// Thrown when the randomizer function returns a value outside the range [0, 1). + /// + /// + /// + /// let inputs = [| 0; 1; 2; 3; 4 |] + /// + /// inputs |> Array.randomChoiceBy Random.Shared.NextDouble + /// + /// Can evaluate to 3. + /// + [] + val randomChoiceBy: randomizer: (unit -> float) -> source: 'T array -> 'T + + /// Returns an array of random elements from the given array, each element can be selected multiple times. + /// + /// The number of elements to return. + /// The input array. + /// + /// An array of randomly selected elements from the input array. + /// + /// Thrown when the input array is null. + /// Thrown when the input array is empty. + /// Thrown when count is less than 0. + /// + /// + /// + /// let inputs = [| 0; 1; 2; 3; 4 |] + /// + /// inputs |> Array.randomChoices 3 + /// + /// Can evaluate to [| 3; 1; 3 |]. + /// + [] + val randomChoices: count: int -> source: 'T array -> 'T array + + /// Returns an array of random elements from the given array with the specified Random instance, each element can be selected multiple times. + /// + /// The Random instance. + /// The number of elements to return. + /// The input array. + /// + /// An array of randomly selected elements from the input array. + /// + /// Thrown when the input array is null. + /// Thrown when the random argument is null. + /// Thrown when the input array is empty. + /// Thrown when count is less than 0. + /// + /// + /// + /// let inputs = [| 0; 1; 2; 3; 4 |] + /// + /// inputs |> Array.randomChoicesWith Random.Shared 3 + /// + /// Can evaluate to [| 3; 1; 3 |]. + /// + [] + val randomChoicesWith: random: Random -> count: int -> source: 'T array -> 'T array + + /// Returns an array of random elements from the given array using the specified randomizer function, each element can be selected multiple times. + /// + /// The randomizer function, must return a float number from [0.0..1.0) range. + /// The number of elements to return. + /// The input array. + /// + /// An array of randomly selected elements from the input array. + /// + /// Thrown when the input array is null. + /// Thrown when the input array is empty. + /// Thrown when count is less than 0. + /// Thrown when the randomizer function returns a value outside the range [0, 1). + /// + /// + /// + /// let inputs = [| 0; 1; 2; 3; 4 |] + /// + /// inputs |> Array.randomChoicesBy Random.Shared.NextDouble 3 + /// + /// Can evaluate to [| 3; 1; 3 |]. + /// + [] + val randomChoicesBy: randomizer: (unit -> float) -> count: int -> source: 'T array -> 'T array + + /// Returns a random sample of elements from the given array, each element can be selected only once. + /// + /// The number of elements to return. + /// The input array. + /// + /// An array of randomly selected elements from the input array. + /// + /// Thrown when the input array is null. + /// Thrown when the input array is empty. + /// Thrown when count is less than 0. + /// Thrown when count is greater than the length of the input array. + /// + /// + /// + /// let inputs = [| 0; 1; 2; 3; 4 |] + /// + /// inputs |> Array.randomSample 3 + /// + /// Can evaluate to [| 3; 1; 2 |]. + /// + [] + val randomSample: count: int -> source: 'T array -> 'T array + + /// Returns a random sample of elements from the given array with the specified Random instance, each element can be selected only once. + /// + /// The Random instance. + /// The number of elements to return. + /// The input array. + /// + /// An array of randomly selected elements from the input array. + /// + /// Thrown when the input array is null. + /// Thrown when the random argument is null. + /// Thrown when the input array is empty. + /// Thrown when count is less than 0. + /// Thrown when count is greater than the length of the input array. + /// + /// + /// + /// let inputs = [| 0; 1; 2; 3; 4 |] + /// + /// inputs |> Array.randomSampleWith Random.Shared 3 + /// + /// Can evaluate to [| 3; 1; 2 |]. + /// + [] + val randomSampleWith: random: Random -> count: int -> source: 'T array -> 'T array + + /// Returns a random sample of elements from the given array using the specified randomizer function, each element can be selected only once. + /// + /// The randomizer function, must return a float number from [0.0..1.0) range. + /// The number of elements to return. + /// The input array. + /// + /// An array of randomly selected elements from the input array. + /// + /// Thrown when the input array is null. + /// Thrown when the input array is empty. + /// Thrown when count is less than 0. + /// Thrown when count is greater than the length of the input array. + /// Thrown when the randomizer function returns a value outside the range [0, 1). + /// + /// + /// + /// let inputs = [| 0; 1; 2; 3; 4 |] + /// + /// inputs |> Array.randomSampleBy Random.Shared.NextDouble 3 + /// + /// Can evaluate to [| 3; 1; 2 |]. + /// + [] + val randomSampleBy: randomizer: (unit -> float) -> count: int -> source: 'T array -> 'T array + /// Provides parallel operations on arrays module Parallel = diff --git a/src/FSharp.Core/list.fs b/src/FSharp.Core/list.fs index dc81209a8c7..d502107a6da 100644 --- a/src/FSharp.Core/list.fs +++ b/src/FSharp.Core/list.fs @@ -2,6 +2,7 @@ namespace Microsoft.FSharp.Collections +open System open Microsoft.FSharp.Core open Microsoft.FSharp.Core.Operators open Microsoft.FSharp.Core.LanguagePrimitives @@ -987,3 +988,173 @@ module List = coll.AddMany(values) // insert values BEFORE the item at the index coll.AddManyAndClose(curr) + + [] + let randomShuffleWith (random: Random) (source: 'T list) : 'T list = + checkNonNull "random" random + + let tempArray = toArray source + Microsoft.FSharp.Primitives.Basics.Random.shuffleArrayInPlaceWith random tempArray + ofArray tempArray + + [] + let randomShuffleBy (randomizer: unit -> float) (source: 'T list) : 'T list = + let tempArray = toArray source + Microsoft.FSharp.Primitives.Basics.Random.shuffleArrayInPlaceBy randomizer tempArray + ofArray tempArray + + [] + let randomShuffle (source: 'T list) : 'T list = + randomShuffleWith ThreadSafeRandom.Shared source + + [] + let randomChoiceWith (random: Random) (source: 'T list) : 'T = + checkNonNull "random" random + + let inputLength = source.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString + + let i = random.Next(0, inputLength) + source[i] + + [] + let randomChoiceBy (randomizer: unit -> float) (source: 'T list) : 'T = + let inputLength = source.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString + + let i = Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength + source[i] + + [] + let randomChoice (source: 'T list) : 'T = + randomChoiceWith ThreadSafeRandom.Shared source + + [] + let randomChoicesWith (random: Random) (count: int) (source: 'T list) : 'T list = + checkNonNull "random" random + + if count < 0 then + invalidArgInputMustBeNonNegative "count" count + + let inputLength = source.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString + + [ + for _ = 0 to count - 1 do + let j = random.Next(0, inputLength) + source[j] + ] + + [] + let randomChoicesBy (randomizer: unit -> float) (count: int) (source: 'T list) : 'T list = + if count < 0 then + invalidArgInputMustBeNonNegative "count" count + + let inputLength = source.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString + + [ + for _ = 0 to count - 1 do + let j = Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength + source[j] + ] + + [] + let randomChoices (count: int) (source: 'T list) : 'T list = + randomChoicesWith ThreadSafeRandom.Shared count source + + [] + let randomSampleWith (random: Random) (count: int) (source: 'T list) : 'T list = + checkNonNull "random" random + + if count < 0 then + invalidArgInputMustBeNonNegative "count" count + + let inputLength = source.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString + + if count > inputLength then + invalidArg "count" (SR.GetString(SR.notEnoughElements)) + + // algorithm taken from https://github.com/python/cpython/blob/69b3e8ea569faabccd74036e3d0e5ec7c0c62a20/Lib/random.py#L363-L456 + let setSize = + Microsoft.FSharp.Primitives.Basics.Random.getMaxSetSizeForSampling count + + if inputLength <= setSize then + let pool = source |> toArray + + [ + for i = 0 to count - 1 do + let j = random.Next(0, inputLength - i) + let item = pool[j] + pool[j] <- pool[inputLength - i - 1] + item + ] + else + let selected = HashSet() + + [ + for _ = 0 to count - 1 do + let mutable j = random.Next(0, inputLength) + + while not (selected.Add j) do + j <- random.Next(0, inputLength) + + source[j] + ] + + [] + let randomSampleBy (randomizer: unit -> float) (count: int) (source: 'T list) : 'T list = + if count < 0 then + invalidArgInputMustBeNonNegative "count" count + + let inputLength = source.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString + + if count > inputLength then + invalidArg "count" (SR.GetString(SR.notEnoughElements)) + + let setSize = + Microsoft.FSharp.Primitives.Basics.Random.getMaxSetSizeForSampling count + + if inputLength <= setSize then + let pool = source |> toArray + + [ + for i = 0 to count - 1 do + let j = + Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 (inputLength - i) + + let item = pool[j] + pool[j] <- pool[inputLength - i - 1] + item + ] + else + let selected = HashSet() + + [ + for _ = 0 to count - 1 do + let mutable j = + Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength + + while not (selected.Add j) do + j <- Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength + + source[j] + ] + + [] + let randomSample (count: int) (source: 'T list) : 'T list = + randomSampleWith ThreadSafeRandom.Shared count source diff --git a/src/FSharp.Core/list.fsi b/src/FSharp.Core/list.fsi index 05ff605b247..5393fd14439 100644 --- a/src/FSharp.Core/list.fsi +++ b/src/FSharp.Core/list.fsi @@ -2694,3 +2694,258 @@ module List = /// [] val insertManyAt: index: int -> values: seq<'T> -> source: 'T list -> 'T list + + /// Return a new list shuffled in a random order. + /// + /// The input list. + /// + /// The result list. + /// + /// + /// + /// let inputs = [ 0; 1; 2; 3; 4 ] + /// + /// inputs |> List.randomShuffle + /// Can evaluate to [ 0; 2; 4; 3; 1 ]. + /// + /// + [] + val randomShuffle : source: 'T list -> 'T list + + /// Return a new list shuffled in a random order with the specified Random instance. + /// + /// The Random instance. + /// The input list. + /// + /// The result list. + /// + /// Thrown when the random argument is null. + /// + /// + /// + /// let inputs = [ 0; 1; 2; 3; 4 ] + /// + /// inputs |> List.randomShuffleWith Random.Shared + /// + /// Can evaluate to [ 0; 2; 4; 3; 1 ]. + /// + [] + val randomShuffleWith : random: Random -> source: 'T list -> 'T list + + /// Return a new list shuffled in a random order using the specified randomizer function. + /// + /// The randomizer function, must return a float number from [0.0..1.0) range. + /// The input list. + /// + /// The result list. + /// + /// Thrown when the randomizer function returns a value outside the range [0, 1). + /// + /// + /// + /// let inputs = [ 0; 1; 2; 3; 4 ] + /// + /// inputs |> List.randomShuffleBy Random.Shared.NextDouble + /// + /// Can evaluate to [ 0; 2; 4; 3; 1 ]. + /// + [] + val randomShuffleBy : randomizer: (unit -> float) -> source: 'T list -> 'T list + + /// Returns a random element from the given list. + /// + /// The input list. + /// + /// A randomly selected element from the input list. + /// + /// Thrown when the input list is empty. + /// + /// + /// + /// let inputs = [ 0; 1; 2; 3; 4 ] + /// + /// inputs |> List.randomChoice + /// + /// Can evaluate to 3. + /// + [] + val randomChoice : source: 'T list -> 'T + + /// Returns a random element from the given list with the specified Random instance, each element can be selected multiple times. + /// + /// The Random instance. + /// The input list. + /// + /// A randomly selected element from the input list. + /// + /// Thrown when the random argument is null. + /// Thrown when the input list is empty. + /// + /// + /// + /// let inputs = [ 0; 1; 2; 3; 4 ] + /// + /// inputs |> List.randomChoiceWith Random.Shared + /// + /// Can evaluate to 3. + /// + [] + val randomChoiceWith : random: Random -> source: 'T list -> 'T + + /// Returns a random element from the given list using the specified randomizer function. + /// + /// The randomizer function, must return a float number from [0.0..1.0) range. + /// The input list. + /// + /// A randomly selected element from the input list. + /// + /// Thrown when the input list is empty. + /// Thrown when the randomizer function returns a value outside the range [0, 1). + /// + /// + /// + /// let inputs = [ 0; 1; 2; 3; 4 ] + /// + /// inputs |> List.randomChoiceBy Random.Shared.NextDouble + /// + /// Can evaluate to 3. + /// + [] + val randomChoiceBy : randomizer: (unit -> float) -> source: 'T list -> 'T + + /// Returns a list of random elements from the given list. + /// + /// The number of elements to return. + /// The input list. + /// + /// A list of randomly selected elements from the input list. + /// + /// Thrown when the input list is empty. + /// Thrown when count is less than 0. + /// + /// + /// + /// let inputs = [ 0; 1; 2; 3; 4 ] + /// + /// inputs |> List.randomChoices 3 + /// + /// Can evaluate to [ 3; 1; 3 ]. + /// + [] + val randomChoices : count: int -> source: 'T list -> 'T list + + /// Returns a list of random elements from the given list with the specified Random instance, each element can be selected multiple times. + /// + /// The Random instance. + /// The number of elements to return. + /// The input list. + /// + /// A list of randomly selected elements from the input list. + /// + /// Thrown when the random argument is null. + /// Thrown when the input list is empty. + /// Thrown when count is less than 0. + /// + /// + /// + /// let inputs = [ 0; 1; 2; 3; 4 ] + /// + /// inputs |> Array.randomChoicesWith Random.Shared 3 + /// + /// Can evaluate to [ 3; 1; 3 ]. + /// + [] + val randomChoicesWith : random: Random -> count: int -> source: 'T list -> 'T list + + /// Returns a list of random elements from the given list using the specified randomizer function. + /// + /// The randomizer function, must return a float number from [0.0..1.0) range. + /// The number of elements to return. + /// The input list. + /// + /// A list of randomly selected elements from the input list. + /// + /// Thrown when the input list is empty. + /// Thrown when count is less than 0. + /// Thrown when the randomizer function returns a value outside the range [0, 1). + /// + /// + /// + /// let inputs = [ 0; 1; 2; 3; 4 ] + /// + /// inputs |> List.randomChoicesBy Random.Shared.NextDouble 3 + /// + /// Can evaluate to [ 3; 1; 3 ]. + /// + [] + val randomChoicesBy : randomizer: (unit -> float) -> count: int -> source: 'T list -> 'T list + + /// Returns a random sample of elements from the given list, each element can be selected only once. + /// + /// The number of elements to return. + /// The input list. + /// + /// A list of randomly selected elements from the input list. + /// + /// Thrown when the input list is empty. + /// Thrown when count is less than 0. + /// Thrown when count is greater than the length of the input list. + /// + /// + /// + /// let inputs = [ 0; 1; 2; 3; 4 ] + /// + /// inputs |> List.randomSample 3 + /// + /// Can evaluate to [ 3; 1; 2 ]. + /// + [] + val randomSample : count: int -> source: 'T list -> 'T list + + /// Returns a random sample of elements from the given list with the specified Random instance, each element can be selected only once. + /// + /// The Random instance. + /// The number of elements to return. + /// The input list. + /// + /// A list of randomly selected elements from the input list. + /// + /// Thrown when the random argument is null. + /// Thrown when the input list is empty. + /// Thrown when count is less than 0. + /// Thrown when count is greater than the length of the input list. + /// + /// + /// + /// let inputs = [ 0; 1; 2; 3; 4 ] + /// + /// inputs |> List.randomSampleWith Random.Shared 3 + /// + /// Can evaluate to [ 3; 1; 2 ]. + /// + [] + val randomSampleWith : random: Random -> count: int -> source: 'T list -> 'T list + + /// Returns a random sample of elements from the given list using the specified randomizer function, each element can be selected only once. + /// + /// The randomizer function, must return a float number from [0.0..1.0) range. + /// The number of elements to return. + /// The input list. + /// + /// A list of randomly selected elements from the input list. + /// + /// Thrown when the input list is empty. + /// Thrown when count is less than 0. + /// Thrown when count is greater than the length of the input list. + /// Thrown when the randomizer function returns a value outside the range [0, 1). + /// + /// + /// + /// let inputs = [ 0; 1; 2; 3; 4 ] + /// + /// inputs |> List.randomSampleBy Random.Shared.NextDouble 3 + /// + /// Can evaluate to [ 3; 1; 2 ]. + /// + [] + val randomSampleBy : randomizer: (unit -> float) -> count: int -> source: 'T list -> 'T list \ No newline at end of file diff --git a/src/FSharp.Core/local.fs b/src/FSharp.Core/local.fs index 063feb4b243..1e7df3c2ec8 100644 --- a/src/FSharp.Core/local.fs +++ b/src/FSharp.Core/local.fs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - +// Copyright © 2001-2023 Python Software Foundation. All Rights Reserved. License: https://docs.python.org/3/license.html#psf-license. Code for getMaxSetSizeForSampling is taken from https://github.com/python/cpython/blob/69b3e8ea569faabccd74036e3d0e5ec7c0c62a20/Lib/random.py#L363-L456 namespace Microsoft.FSharp.Core @@ -1199,4 +1199,44 @@ module internal Seq = while (e.MoveNext()) do res <- e.Current ValueSome(res) else - ValueNone \ No newline at end of file + ValueNone + +module internal Random = + open System + + let private executeRandomizer (randomizer: unit -> float) = + let value = randomizer() + if value >= 0.0 && value < 1.0 then + value + else + let argName = nameof randomizer + invalidArgOutOfRangeFmt argName + "{0}\n{1} returned {2}, should be in range [0.0, 1.0)." + [|SR.GetString SR.outOfRange; argName; value|] + + let next (randomizer: unit -> float) (minValue: int) (maxValue: int) = + int ((executeRandomizer randomizer) * float (maxValue - minValue)) + minValue + + let shuffleArrayInPlaceWith (random: Random) (array: array<'T>) = + let inputLength = array.Length + for i = 0 to inputLength - 2 do + let j = random.Next(i, inputLength) + if j <> i then + let temp = array[i] + array[i] <- array[j] + array[j] <- temp + + let shuffleArrayInPlaceBy (randomizer: unit -> float) (array: array<'T>) = + let inputLength = array.Length + for i = 0 to inputLength - 2 do + let j = next randomizer i inputLength + if j <> i then + let temp = array[i] + array[i] <- array[j] + array[j] <- temp + + let getMaxSetSizeForSampling count = + let mutable setSize = 21 + if count > 5 then + setSize <- setSize + (4.0 ** ceil (Math.Log(count * 3 |> float, 4)) |> int) + setSize \ No newline at end of file diff --git a/src/FSharp.Core/local.fsi b/src/FSharp.Core/local.fsi index 1b210474062..9de6b8c8152 100644 --- a/src/FSharp.Core/local.fsi +++ b/src/FSharp.Core/local.fsi @@ -26,6 +26,7 @@ module internal DetailedExceptions = // Definitions internal for this library. namespace Microsoft.FSharp.Primitives.Basics +open System open Microsoft.FSharp.Core open Microsoft.FSharp.Collections @@ -119,5 +120,13 @@ module internal Array = val stableSortInPlace: array: 'T array -> unit when 'T: comparison +module internal Random = + + val next: randomizer: (unit -> float) -> minValue: int -> maxValue: int -> int + val getMaxSetSizeForSampling: count: int -> int + + val shuffleArrayInPlaceWith: random: Random -> array: 'T[] -> unit + val shuffleArrayInPlaceBy: randomizer: (unit -> float) -> array: 'T[] -> unit + module internal Seq = val tryLastV: 'T seq -> 'T ValueOption diff --git a/src/FSharp.Core/seq.fs b/src/FSharp.Core/seq.fs index bfe050f9f7c..35adcdda557 100644 --- a/src/FSharp.Core/seq.fs +++ b/src/FSharp.Core/seq.fs @@ -1938,3 +1938,187 @@ module Seq = if i < index then invalidArg "index" "index must be within bounds of the array" } + + [] + let randomShuffleWith (random: Random) (source: seq<'T>) : seq<'T> = + checkNonNull "random" random + checkNonNull "source" source + + let tempArray = toArray source + Microsoft.FSharp.Primitives.Basics.Random.shuffleArrayInPlaceWith random tempArray + ofArray tempArray + + [] + let randomShuffleBy (randomizer: unit -> float) (source: seq<'T>) : seq<'T> = + checkNonNull "source" source + + let tempArray = toArray source + Microsoft.FSharp.Primitives.Basics.Random.shuffleArrayInPlaceBy randomizer tempArray + ofArray tempArray + + [] + let randomShuffle (source: seq<'T>) : seq<'T> = + randomShuffleWith ThreadSafeRandom.Shared source + + [] + let randomChoiceWith (random: Random) (source: seq<'T>) : 'T = + checkNonNull "random" random + checkNonNull "source" source + + let tempArray = toArray source + let inputLength = tempArray.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString + + let i = random.Next(0, inputLength) + tempArray[i] + + [] + let randomChoiceBy (randomizer: unit -> float) (source: seq<'T>) : 'T = + checkNonNull "source" source + + let tempArray = toArray source + let inputLength = tempArray.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString + + let i = Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength + tempArray[i] + + [] + let randomChoice (source: seq<'T>) : 'T = + randomChoiceWith ThreadSafeRandom.Shared source + + [] + let randomChoicesWith (random: Random) (count: int) (source: seq<'T>) : seq<'T> = + checkNonNull "random" random + checkNonNull "source" source + + if count < 0 then + invalidArgInputMustBeNonNegative "count" count + + let tempArray = toArray source + let inputLength = tempArray.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString + + seq { + for _ = 0 to count - 1 do + let j = random.Next(0, inputLength) + tempArray[j] + } + + [] + let randomChoicesBy (randomizer: unit -> float) (count: int) (source: seq<'T>) : seq<'T> = + checkNonNull "source" source + + if count < 0 then + invalidArgInputMustBeNonNegative "count" count + + let tempArray = toArray source + let inputLength = tempArray.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString + + seq { + for _ = 0 to count - 1 do + let j = Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength + tempArray[j] + } + + [] + let randomChoices (count: int) (source: seq<'T>) : seq<'T> = + randomChoicesWith ThreadSafeRandom.Shared count source + + [] + let randomSampleWith (random: Random) (count: int) (source: seq<'T>) : seq<'T> = + checkNonNull "random" random + checkNonNull "source" source + + if count < 0 then + invalidArgInputMustBeNonNegative "count" count + + let tempArray = toArray source + let inputLength = tempArray.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString + + if count > inputLength then + invalidArg "count" (SR.GetString(SR.notEnoughElements)) + + // algorithm taken from https://github.com/python/cpython/blob/69b3e8ea569faabccd74036e3d0e5ec7c0c62a20/Lib/random.py#L363-L456 + let setSize = + Microsoft.FSharp.Primitives.Basics.Random.getMaxSetSizeForSampling count + + if inputLength <= setSize then + seq { + for i = 0 to count - 1 do + let j = random.Next(0, inputLength - i) + let item = tempArray[j] + tempArray[j] <- tempArray[inputLength - i - 1] + item + } + else + let selected = HashSet() + + seq { + for _ = 0 to count - 1 do + let mutable j = random.Next(0, inputLength) + + while not (selected.Add j) do + j <- random.Next(0, inputLength) + + tempArray[j] + } + + [] + let randomSampleBy (randomizer: unit -> float) (count: int) (source: seq<'T>) : seq<'T> = + checkNonNull "source" source + + if count < 0 then + invalidArgInputMustBeNonNegative "count" count + + let tempArray = toArray source + let inputLength = tempArray.Length + + if inputLength = 0 then + invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString + + if count > inputLength then + invalidArg "count" (SR.GetString(SR.notEnoughElements)) + + let setSize = + Microsoft.FSharp.Primitives.Basics.Random.getMaxSetSizeForSampling count + + if inputLength <= setSize then + seq { + for i = 0 to count - 1 do + let j = + Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 (inputLength - i) + + let item = tempArray[j] + tempArray[j] <- tempArray[inputLength - i - 1] + item + } + else + let selected = HashSet() + + seq { + for _ = 0 to count - 1 do + let mutable j = + Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength + + while not (selected.Add j) do + j <- Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength + + tempArray[j] + } + + [] + let randomSample (count: int) (source: seq<'T>) : seq<'T> = + randomSampleWith ThreadSafeRandom.Shared count source diff --git a/src/FSharp.Core/seq.fsi b/src/FSharp.Core/seq.fsi index 4bdf2a54d6d..c1e5a6f0ba9 100644 --- a/src/FSharp.Core/seq.fsi +++ b/src/FSharp.Core/seq.fsi @@ -2935,3 +2935,289 @@ module Seq = /// [] val insertManyAt: index: int -> values: seq<'T> -> source: seq<'T> -> seq<'T> + + /// Return a new sequence shuffled in a random order. + /// + /// The input sequence. + /// + /// The result sequence. + /// + /// Thrown when the input sequence is null. + /// + /// + /// + /// let inputs = seq { 0; 1; 2; 3; 4 } + /// + /// inputs |> Seq.randomShuffle + /// + /// Can evaluate to seq { 0; 2; 4; 3; 1 }. + /// + [] + val randomShuffle: source: seq<'T> -> seq<'T> + + /// Return a new sequence shuffled in a random order with the specified Random instance. + /// + /// The Random instance. + /// The input sequence. + /// + /// The result sequence. + /// + /// Thrown when the input sequence is null. + /// Thrown when the random argument is null. + /// + /// + /// + /// let inputs = seq { 0; 1; 2; 3; 4 } + /// + /// inputs |> Seq.randomShuffleWith Random.Shared + /// + /// Can evaluate to seq { 0; 2; 4; 3; 1 }. + /// + [] + val randomShuffleWith: random: Random -> source: seq<'T> -> seq<'T> + + /// Return a new sequence shuffled in a random order with the specified randomizer function. + /// + /// The randomizer function, must return a float number from [0.0..1.0) range. + /// The input sequence. + /// + /// The result sequence. + /// Thrown when the input sequence is null. + /// Thrown when the randomizer function returns a number outside the range [0.0..1.0). + /// + /// + /// + /// let inputs = seq { 0; 1; 2; 3; 4 } + /// + /// inputs |> Seq.randomShuffleBy Random.Shared.NextDouble + /// + /// Can evaluate to seq { 0; 2; 4; 3; 1 }. + /// + [] + val randomShuffleBy: randomizer: (unit -> float) -> source: seq<'T> -> seq<'T> + + /// + /// Returns a random element from the given sequence. + /// + /// + /// The input sequence. + /// + /// A randomly selected element from the input sequence. + /// + /// Thrown when the input sequence is null. + /// Thrown when the input sequence is empty. + /// + /// + /// + /// let inputs = seq { 0; 1; 2; 3; 4 } + /// + /// inputs |> Seq.randomChoice + /// + /// Can evaluate to 3. + /// + [] + val randomChoice: source: seq<'T> -> 'T + + /// + /// Returns a random element from the given sequence with the specified Random instance. + /// + /// + /// The Random instance. + /// The input sequence. + /// + /// A randomly selected element from the input array. + /// + /// Thrown when the input sequence is null. + /// Thrown when the random argument is null. + /// Thrown when the input sequence is empty. + /// + /// + /// + /// let inputs = seq { 0; 1; 2; 3; 4 } + /// + /// inputs |> Seq.randomChoiceWith Random.Shared + /// + /// Can evaluate to 3. + /// + [] + val randomChoiceWith: random: Random -> source: seq<'T> -> 'T + + /// + /// Returns a random element from the given sequence with the specified randomizer function. + /// + /// + /// The randomizer function, must return a float number from [0.0..1.0) range. + /// The input sequence. + /// + /// A randomly selected element from the input sequence. + /// + /// Thrown when the input sequence is null. + /// Thrown when the input sequence is empty. + /// Thrown when the randomizer function returns a number outside the range [0.0..1.0). + /// + /// + /// + /// let inputs = seq { 0; 1; 2; 3; 4 } + /// let randomizer = Random.Shared.NextDouble + /// + /// inputs |> Seq.randomChoiceBy randomizer + /// + /// Can evaluate to 3. + /// + [] + val randomChoiceBy: randomizer: (unit -> float) -> source: seq<'T> -> 'T + + /// + /// Returns an sequence of random elements from the given sequence, each element can be selected multiple times. + /// + /// + /// The number of elements to return. + /// The input sequence. + /// + /// A sequence of randomly selected elements from the input sequence. + /// + /// Thrown when the input sequence is null. + /// Thrown when the input sequence is empty. + /// Thrown when count is less than 0. + /// + /// + /// + /// let inputs = seq { 0; 1; 2; 3; 4 } + /// + /// inputs |> Seq.randomChoices 3 + /// + /// Can evaluate to seq { 3; 1; 3 }. + /// + [] + val randomChoices: count: int -> source: seq<'T> -> seq<'T> + + /// + /// Returns a sequence of random elements from the given sequence with the specified Random instance, each element can be selected multiple times. + /// + /// + /// The Random instance. + /// The number of elements to return. + /// The input sequence. + /// + /// A sequence of randomly selected elements from the input sequence. + /// + /// Thrown when the input sequence is null. + /// Thrown when the random argument is null. + /// Thrown when the input sequence is empty. + /// Thrown when count is less than 0. + /// + /// + /// + /// let inputs = seq { 0; 1; 2; 3; 4 } + /// + /// inputs |> Seq.randomChoicesWith Random.Shared 3 + /// + /// Can evaluate to seq { 3; 1; 3 }. + /// + [] + val randomChoicesWith: random: Random -> count: int -> source: seq<'T> -> seq<'T> + + /// + /// Returns a sequence of random elements from the given sequence with the specified randomizer function, each element can be selected multiple times. + /// + /// + /// The randomizer function, must return a float number from [0.0..1.0) range. + /// The number of elements to return. + /// The input sequence. + /// + /// A sequence of randomly selected elements from the input sequence. + /// + /// Thrown when the input sequence is null. + /// Thrown when the input sequence is empty. + /// Thrown when count is less than 0. + /// Thrown when the randomizer function returns a number outside the range [0.0..1.0). + /// + /// + /// + /// let inputs = seq { 0; 1; 2; 3; 4 } + /// + /// inputs |> Seq.randomChoicesBy Random.Shared.NextDouble 3 + /// + /// Can evaluate to seq { 3; 1; 3 }. + /// + [] + val randomChoicesBy: randomizer: (unit -> float) -> count: int -> source: seq<'T> -> seq<'T> + + /// + /// Returns a random sample of elements from the given sequence, each element can be selected only once. + /// + /// + /// The number of elements to return. + /// The input sequence. + /// + /// A sequence of randomly selected elements from the input sequence. + /// + /// Thrown when the input sequence is null. + /// Thrown when the input sequence is empty. + /// Thrown when count is less than 0. + /// Thrown when count is greater than the length of the input sequence. + /// + /// + /// + /// let inputs = seq { 0; 1; 2; 3; 4 } + /// + /// inputs |> Seq.randomSample 3 + /// + /// Can evaluate to seq { 3; 1; 2 }. + /// + [] + val randomSample: count: int -> source: seq<'T> -> seq<'T> + + /// + /// Returns a random sample of elements from the given sequence with the specified Random instance, each element can be selected only once. + /// + /// + /// The Random instance. + /// The number of elements to return. + /// The input sequence. + /// + /// A sequence of randomly selected elements from the input sequence. + /// + /// Thrown when the input sequence is null. + /// Thrown when the random argument is null. + /// Thrown when the input sequence is empty. + /// Thrown when count is less than 0. + /// Thrown when count is greater than the length of the input sequence. + /// + /// + /// + /// let inputs = seq { 0; 1; 2; 3; 4 } + /// + /// inputs |> Seq.randomSampleWith Random.Shared 3 + /// + /// Can evaluate to seq { 3; 1; 2 }. + /// + [] + val randomSampleWith: random: Random -> count: int -> source: seq<'T> -> seq<'T> + + /// + /// Returns a random sample of elements from the given sequence with the specified randomizer function, each element can be selected only once. + /// + /// + /// The randomizer function, must return a float number from [0.0..1.0) range. + /// The number of elements to return. + /// The input sequence. + /// + /// A sequence of randomly selected elements from the input sequence. + /// + /// Thrown when the input sequence is null. + /// Thrown when the input sequence is empty. + /// Thrown when count is less than 0. + /// Thrown when count is greater than the length of the input sequence. + /// Thrown when the randomizer function returns a number outside the range [0.0..1.0). + /// + /// + /// + /// let inputs = seq { 0; 1; 2; 3; 4 } + /// + /// inputs |> Seq.randomSampleBy Random.Shared.NextDouble 3 + /// + /// Can evaluate to seq { 3; 1; 2 }. + /// + [] + val randomSampleBy: randomizer: (unit -> float) -> count: int -> source: seq<'T> -> seq<'T> diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl index bac32123722..e1864618be6 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl @@ -126,6 +126,9 @@ Microsoft.FSharp.Collections.ArrayModule: T MaxBy[T,TResult](Microsoft.FSharp.Co Microsoft.FSharp.Collections.ArrayModule: T Max[T](T[]) Microsoft.FSharp.Collections.ArrayModule: T MinBy[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], T[]) Microsoft.FSharp.Collections.ArrayModule: T Min[T](T[]) +Microsoft.FSharp.Collections.ArrayModule: T RandomChoiceBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], T[]) +Microsoft.FSharp.Collections.ArrayModule: T RandomChoiceWith[T](System.Random, T[]) +Microsoft.FSharp.Collections.ArrayModule: T RandomChoice[T](T[]) Microsoft.FSharp.Collections.ArrayModule: T ReduceBack[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], T[]) Microsoft.FSharp.Collections.ArrayModule: T Reduce[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], T[]) Microsoft.FSharp.Collections.ArrayModule: T Sum$W[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,T], Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], T[]) @@ -164,6 +167,15 @@ Microsoft.FSharp.Collections.ArrayModule: T[] InsertManyAt[T](Int32, System.Coll Microsoft.FSharp.Collections.ArrayModule: T[] OfList[T](Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ArrayModule: T[] OfSeq[T](System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.ArrayModule: T[] Permute[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,System.Int32], T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomChoicesBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Int32, T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomChoicesWith[T](System.Random, Int32, T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomChoices[T](Int32, T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomSampleBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Int32, T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomSampleWith[T](System.Random, Int32, T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomSample[T](Int32, T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomShuffleBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomShuffleWith[T](System.Random, T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomShuffle[T](T[]) Microsoft.FSharp.Collections.ArrayModule: T[] RemoveAt[T](Int32, T[]) Microsoft.FSharp.Collections.ArrayModule: T[] RemoveManyAt[T](Int32, Int32, T[]) Microsoft.FSharp.Collections.ArrayModule: T[] Replicate[T](Int32, T) @@ -194,6 +206,9 @@ Microsoft.FSharp.Collections.ArrayModule: Void Iterate2[T1,T2](Microsoft.FSharp. Microsoft.FSharp.Collections.ArrayModule: Void IterateIndexed2[T1,T2](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T1,Microsoft.FSharp.Core.FSharpFunc`2[T2,Microsoft.FSharp.Core.Unit]]], T1[], T2[]) Microsoft.FSharp.Collections.ArrayModule: Void IterateIndexed[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit]], T[]) Microsoft.FSharp.Collections.ArrayModule: Void Iterate[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit], T[]) +Microsoft.FSharp.Collections.ArrayModule: Void RandomShuffleInPlaceBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], T[]) +Microsoft.FSharp.Collections.ArrayModule: Void RandomShuffleInPlaceWith[T](System.Random, T[]) +Microsoft.FSharp.Collections.ArrayModule: Void RandomShuffleInPlace[T](T[]) Microsoft.FSharp.Collections.ArrayModule: Void Set[T](T[], Int32, T) Microsoft.FSharp.Collections.ArrayModule: Void SortInPlaceBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[]) Microsoft.FSharp.Collections.ArrayModule: Void SortInPlaceWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[]) @@ -329,6 +344,15 @@ Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] OfArray[T](T[]) Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] OfSeq[T](System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] Permute[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,System.Int32], Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomChoicesBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomChoicesWith[T](System.Random, Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomChoices[T](Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomSampleBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomSampleWith[T](System.Random, Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomSample[T](Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomShuffleBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomShuffleWith[T](System.Random, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomShuffle[T](Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RemoveAt[T](Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RemoveManyAt[T](Int32, Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] Replicate[T](Int32, T) @@ -377,6 +401,9 @@ Microsoft.FSharp.Collections.ListModule: T MaxBy[T,TResult](Microsoft.FSharp.Cor Microsoft.FSharp.Collections.ListModule: T Max[T](Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: T MinBy[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: T Min[T](Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: T RandomChoiceBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: T RandomChoiceWith[T](System.Random, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: T RandomChoice[T](Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: T ReduceBack[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: T Reduce[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: T Sum$W[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,T], Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], Microsoft.FSharp.Collections.FSharpList`1[T]) @@ -483,6 +510,15 @@ Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1 Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] OfArray[T](T[]) Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] OfList[T](Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] Permute[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,System.Int32], System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomChoicesBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Int32, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomChoicesWith[T](System.Random, Int32, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomChoices[T](Int32, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomSampleBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Int32, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomSampleWith[T](System.Random, Int32, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomSample[T](Int32, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomShuffleBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomShuffleWith[T](System.Random, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomShuffle[T](System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] ReadOnly[T](System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RemoveAt[T](Int32, System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RemoveManyAt[T](Int32, Int32, System.Collections.Generic.IEnumerable`1[T]) @@ -518,6 +554,9 @@ Microsoft.FSharp.Collections.SeqModule: T MaxBy[T,TResult](Microsoft.FSharp.Core Microsoft.FSharp.Collections.SeqModule: T Max[T](System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: T MinBy[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: T Min[T](System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: T RandomChoiceBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: T RandomChoiceWith[T](System.Random, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: T RandomChoice[T](System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: T ReduceBack[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: T Reduce[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: T Sum$W[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,T], Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], System.Collections.Generic.IEnumerable`1[T]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl index 2d02c4cb1bb..92d85ccd98d 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl @@ -126,6 +126,9 @@ Microsoft.FSharp.Collections.ArrayModule: T MaxBy[T,TResult](Microsoft.FSharp.Co Microsoft.FSharp.Collections.ArrayModule: T Max[T](T[]) Microsoft.FSharp.Collections.ArrayModule: T MinBy[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], T[]) Microsoft.FSharp.Collections.ArrayModule: T Min[T](T[]) +Microsoft.FSharp.Collections.ArrayModule: T RandomChoiceBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], T[]) +Microsoft.FSharp.Collections.ArrayModule: T RandomChoiceWith[T](System.Random, T[]) +Microsoft.FSharp.Collections.ArrayModule: T RandomChoice[T](T[]) Microsoft.FSharp.Collections.ArrayModule: T ReduceBack[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], T[]) Microsoft.FSharp.Collections.ArrayModule: T Reduce[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], T[]) Microsoft.FSharp.Collections.ArrayModule: T Sum$W[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,T], Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], T[]) @@ -164,6 +167,15 @@ Microsoft.FSharp.Collections.ArrayModule: T[] InsertManyAt[T](Int32, System.Coll Microsoft.FSharp.Collections.ArrayModule: T[] OfList[T](Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ArrayModule: T[] OfSeq[T](System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.ArrayModule: T[] Permute[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,System.Int32], T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomChoicesBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Int32, T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomChoicesWith[T](System.Random, Int32, T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomChoices[T](Int32, T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomSampleBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Int32, T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomSampleWith[T](System.Random, Int32, T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomSample[T](Int32, T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomShuffleBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomShuffleWith[T](System.Random, T[]) +Microsoft.FSharp.Collections.ArrayModule: T[] RandomShuffle[T](T[]) Microsoft.FSharp.Collections.ArrayModule: T[] RemoveAt[T](Int32, T[]) Microsoft.FSharp.Collections.ArrayModule: T[] RemoveManyAt[T](Int32, Int32, T[]) Microsoft.FSharp.Collections.ArrayModule: T[] Replicate[T](Int32, T) @@ -194,6 +206,9 @@ Microsoft.FSharp.Collections.ArrayModule: Void Iterate2[T1,T2](Microsoft.FSharp. Microsoft.FSharp.Collections.ArrayModule: Void IterateIndexed2[T1,T2](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T1,Microsoft.FSharp.Core.FSharpFunc`2[T2,Microsoft.FSharp.Core.Unit]]], T1[], T2[]) Microsoft.FSharp.Collections.ArrayModule: Void IterateIndexed[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit]], T[]) Microsoft.FSharp.Collections.ArrayModule: Void Iterate[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit], T[]) +Microsoft.FSharp.Collections.ArrayModule: Void RandomShuffleInPlaceBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], T[]) +Microsoft.FSharp.Collections.ArrayModule: Void RandomShuffleInPlaceWith[T](System.Random, T[]) +Microsoft.FSharp.Collections.ArrayModule: Void RandomShuffleInPlace[T](T[]) Microsoft.FSharp.Collections.ArrayModule: Void Set[T](T[], Int32, T) Microsoft.FSharp.Collections.ArrayModule: Void SortInPlaceBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[]) Microsoft.FSharp.Collections.ArrayModule: Void SortInPlaceWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[]) @@ -329,6 +344,15 @@ Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] OfArray[T](T[]) Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] OfSeq[T](System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] Permute[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,System.Int32], Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomChoicesBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomChoicesWith[T](System.Random, Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomChoices[T](Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomSampleBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomSampleWith[T](System.Random, Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomSample[T](Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomShuffleBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomShuffleWith[T](System.Random, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RandomShuffle[T](Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RemoveAt[T](Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] RemoveManyAt[T](Int32, Int32, Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: Microsoft.FSharp.Collections.FSharpList`1[T] Replicate[T](Int32, T) @@ -377,6 +401,9 @@ Microsoft.FSharp.Collections.ListModule: T MaxBy[T,TResult](Microsoft.FSharp.Cor Microsoft.FSharp.Collections.ListModule: T Max[T](Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: T MinBy[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: T Min[T](Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: T RandomChoiceBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: T RandomChoiceWith[T](System.Random, Microsoft.FSharp.Collections.FSharpList`1[T]) +Microsoft.FSharp.Collections.ListModule: T RandomChoice[T](Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: T ReduceBack[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: T Reduce[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.ListModule: T Sum$W[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,T], Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], Microsoft.FSharp.Collections.FSharpList`1[T]) @@ -483,6 +510,15 @@ Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1 Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] OfArray[T](T[]) Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] OfList[T](Microsoft.FSharp.Collections.FSharpList`1[T]) Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] Permute[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,System.Int32], System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomChoicesBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Int32, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomChoicesWith[T](System.Random, Int32, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomChoices[T](Int32, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomSampleBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], Int32, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomSampleWith[T](System.Random, Int32, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomSample[T](Int32, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomShuffleBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomShuffleWith[T](System.Random, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RandomShuffle[T](System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] ReadOnly[T](System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RemoveAt[T](Int32, System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: System.Collections.Generic.IEnumerable`1[T] RemoveManyAt[T](Int32, Int32, System.Collections.Generic.IEnumerable`1[T]) @@ -518,6 +554,9 @@ Microsoft.FSharp.Collections.SeqModule: T MaxBy[T,TResult](Microsoft.FSharp.Core Microsoft.FSharp.Collections.SeqModule: T Max[T](System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: T MinBy[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: T Min[T](System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: T RandomChoiceBy[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Double], System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: T RandomChoiceWith[T](System.Random, System.Collections.Generic.IEnumerable`1[T]) +Microsoft.FSharp.Collections.SeqModule: T RandomChoice[T](System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: T ReduceBack[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: T Reduce[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], System.Collections.Generic.IEnumerable`1[T]) Microsoft.FSharp.Collections.SeqModule: T Sum$W[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,T], Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,T]], System.Collections.Generic.IEnumerable`1[T]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule.fs index aa112e0b98b..13590a61f5c 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule.fs @@ -1962,3 +1962,436 @@ type ArrayModule() = Assert.AreEqual(arr.[-4..(-3)], ([||]: int array)) + [] + member _.RandomShuffle() = + let arr = [| 1..20 |] + + let shuffled1 = arr |> Array.randomShuffle + let shuffled2 = arr |> Array.randomShuffle + + Assert.AreNotEqual(shuffled1, arr) + Assert.AreNotEqual(shuffled1, shuffled2) + + [] + member _.RandomShuffleWrongArg() = + let nullArr = null + CheckThrowsArgumentNullException (fun () -> Array.randomShuffle nullArr |> ignore) + + [] + member _.RandomShuffleWith() = + let arr = [| 1..20 |] + + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let shuffle1 = arr |> Array.randomShuffleWith rand1 + let shuffle2 = arr |> Array.randomShuffleWith rand2 + let shuffle3 = arr |> Array.randomShuffleWith rand3 + + Assert.AreEqual(shuffle1, shuffle2) + Assert.AreNotEqual(arr, shuffle1) + Assert.AreNotEqual(shuffle1, shuffle3) + + [] + member _.RandomShuffleWithWrongArg() = + let nullArr = null + let arr = [| 1..20 |] + let nullRand = null + let rand = Random(123) + + CheckThrowsArgumentNullException (fun () -> Array.randomShuffleWith rand nullArr |> ignore) + CheckThrowsArgumentNullException (fun () -> Array.randomShuffleWith nullRand arr |> ignore) + + [] + member _.RandomShuffleBy() = + let arr = [| 1..20 |] + + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let shuffle1 = arr |> Array.randomShuffleBy rand1.NextDouble + let shuffle2 = arr |> Array.randomShuffleBy rand2.NextDouble + let shuffle3 = arr |> Array.randomShuffleBy rand3.NextDouble + + Assert.AreEqual(shuffle1, shuffle2) + Assert.AreNotEqual(arr, shuffle1) + Assert.AreNotEqual(shuffle1, shuffle3) + + [] + member _.RandomShuffleByWrongArg() = + let nullArr = null + let arr = [| 1..20 |] + let wrongRandomizers = [ + fun () -> nan + fun () -> 1.0 + fun () -> infinity + ] + let randomizer = Random(123).NextDouble + + CheckThrowsArgumentNullException (fun () -> Array.randomShuffleBy randomizer nullArr |> ignore) + wrongRandomizers |> List.iter (fun wrongRandomizer -> + CheckThrowsArgumentOutOfRangeException (fun () -> Array.randomShuffleBy wrongRandomizer arr |> ignore)) + + [] + member _.RandomShuffleInPlace() = + let arr = [| 1..20 |] + let shuffled1 = [| 1..20 |] + let shuffled2 = [| 1..20 |] + + shuffled1 |> Array.randomShuffleInPlace + shuffled2 |> Array.randomShuffleInPlace + + Assert.AreNotEqual(shuffled1, arr) + Assert.AreNotEqual(shuffled1, shuffled2) + + [] + member _.RandomShuffleInPlaceWrongArg() = + let nullArr = null + + CheckThrowsArgumentNullException (fun () -> Array.randomShuffleInPlace nullArr |> ignore) + + [] + member _.RandomShuffleInPlaceWith() = + let arr = [| 1..20 |] + + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let shuffle1 = [| 1..20 |] + let shuffle2 = [| 1..20 |] + let shuffle3 = [| 1..20 |] + + shuffle1 |> Array.randomShuffleInPlaceWith rand1 + shuffle2 |> Array.randomShuffleInPlaceWith rand2 + shuffle3 |> Array.randomShuffleInPlaceWith rand3 + + Assert.AreEqual(shuffle1, shuffle2) + Assert.AreNotEqual(arr, shuffle1) + Assert.AreNotEqual(shuffle1, shuffle3) + + [] + member _.RandomShuffleInPlaceWithWrongArg() = + let nullArr = null + let arr = [| 1..20 |] + let rand = Random(123) + let nullRand = null + + CheckThrowsArgumentNullException (fun () -> Array.randomShuffleInPlaceWith rand nullArr |> ignore) + CheckThrowsArgumentNullException (fun () -> Array.randomShuffleInPlaceWith nullRand arr |> ignore) + + [] + member _.RandomShuffleInPlaceBy() = + let arr = [| 1..20 |] + + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let shuffle1 = [| 1..20 |] + let shuffle2 = [| 1..20 |] + let shuffle3 = [| 1..20 |] + + shuffle1 |> Array.randomShuffleInPlaceBy rand1.NextDouble + shuffle2 |> Array.randomShuffleInPlaceBy rand2.NextDouble + shuffle3 |> Array.randomShuffleInPlaceBy rand3.NextDouble + + Assert.AreEqual(shuffle1, shuffle2) + Assert.AreNotEqual(arr, shuffle1) + Assert.AreNotEqual(shuffle1, shuffle3) + + [] + member _.RandomShuffleInPlaceByWrongArg() = + let nullArr = null + let arr = [| 1..20 |] + let wrongRandomizers = [ + fun () -> nan + fun () -> 1.0 + fun () -> infinity + ] + let randomizer = Random(123).NextDouble + + CheckThrowsArgumentNullException (fun () -> Array.randomShuffleInPlaceBy randomizer nullArr |> ignore) + wrongRandomizers |> List.iter (fun wrongRandomizer -> + CheckThrowsArgumentOutOfRangeException (fun () -> Array.randomShuffleInPlaceBy wrongRandomizer arr |> ignore)) + + [] + member _.RandomChoice() = + let arr = [| 1..5000 |] + + // try choice five times, if all are same, it must be broken + let results = [| + Array.randomChoice arr + Array.randomChoice arr + Array.randomChoice arr + Array.randomChoice arr + Array.randomChoice arr + |] + let allSame = results |> Array.forall (fun x -> x = results.[0]) + Assert.False(allSame) + + [] + member _.RandomChoiceWrongArg() = + let nullArr = null + let emptyArr = [||] + + CheckThrowsArgumentNullException (fun () -> Array.randomChoice nullArr |> ignore) + CheckThrowsArgumentException (fun () -> Array.randomChoice emptyArr |> ignore) + + [] + member _.RandomChoiceWith() = + let arr = [| 1..5000 |] + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choice1 = arr |> Array.randomChoiceWith rand1 + let choice2 = arr |> Array.randomChoiceWith rand2 + let choice3 = arr |> Array.randomChoiceWith rand3 + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + + [] + member _.RandomChoiceWithWrongArg() = + let nullArr = null + let emptyArr = [||] + let arr = [| 1..20 |] + let nullRand = null + let rand = Random(123) + + CheckThrowsArgumentNullException (fun () -> Array.randomChoiceWith rand nullArr |> ignore) + CheckThrowsArgumentNullException (fun () -> Array.randomChoiceWith nullRand arr |> ignore) + CheckThrowsArgumentException (fun () -> Array.randomChoiceWith rand emptyArr |> ignore) + + [] + member _.RandomChoiceBy() = + let arr = [| 1..5000 |] + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choice1 = arr |> Array.randomChoiceBy rand1.NextDouble + let choice2 = arr |> Array.randomChoiceBy rand2.NextDouble + let choice3 = arr |> Array.randomChoiceBy rand3.NextDouble + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + + [] + member _.RandomChoiceByWrongArg() = + let nullArr = null + let emptyArr = [||] + let arr = [| 1..20 |] + let wrongRandomizers = [ + fun () -> nan + fun () -> 1.0 + fun () -> infinity + ] + let randomizer = Random(123).NextDouble + + CheckThrowsArgumentNullException (fun () -> Array.randomChoiceBy randomizer nullArr |> ignore) + wrongRandomizers |> List.iter (fun wrongRandomizer -> + CheckThrowsArgumentOutOfRangeException (fun () -> Array.randomChoiceBy wrongRandomizer arr |> ignore)) + CheckThrowsArgumentException (fun () -> Array.randomChoiceBy randomizer emptyArr |> ignore) + + [] + member _.RandomChoices() = + let arr = [| 1..50 |] + + let choicesLength = 20 + let choice1 = arr |> Array.randomChoices choicesLength + let choice2 = arr |> Array.randomChoices choicesLength + + Assert.AreNotEqual(choice1, choice2) + Assert.AreEqual(choicesLength, choice1.Length) + Assert.AreEqual(choicesLength, choice2.Length) + + let arr = [| 1; 2 |] + let choices = arr |> Array.randomChoices choicesLength + Assert.AreEqual(choicesLength, choices.Length) + Assert.AreEqual(arr, choices |> Array.distinct |> Array.sort) + + [] + member _.RandomChoicesWrongArg() = + let nullArr = null + let emptyArr = [||] + let arr = [| 1..50 |] + let choicesLength = 20 + let negativeChoicesLength = -1 + + CheckThrowsArgumentNullException (fun () -> Array.randomChoices choicesLength nullArr |> ignore) + CheckThrowsArgumentException (fun () -> Array.randomChoices choicesLength emptyArr |> ignore) + CheckThrowsArgumentException (fun () -> Array.randomChoices negativeChoicesLength arr |> ignore) + + [] + member _.RandomChoicesWith() = + let arr = [| 1..50 |] + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choicesLength = 20 + let choice1 = arr |> Array.randomChoicesWith rand1 choicesLength + let choice2 = arr |> Array.randomChoicesWith rand2 choicesLength + let choice3 = arr |> Array.randomChoicesWith rand3 choicesLength + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + + [] + member _.RandomChoicesWithWrongArg() = + let nullArr = null + let emptyArr = [||] + let arr = [| 1..50 |] + let nullRand = null + let rand = Random(123) + let choicesLength = 20 + let negativeChoicesLength = -1 + + CheckThrowsArgumentNullException (fun () -> Array.randomChoicesWith rand choicesLength nullArr |> ignore) + CheckThrowsArgumentNullException (fun () -> Array.randomChoicesWith nullRand choicesLength arr |> ignore) + CheckThrowsArgumentException (fun () -> Array.randomChoicesWith rand choicesLength emptyArr |> ignore) + CheckThrowsArgumentException (fun () -> Array.randomChoicesWith rand negativeChoicesLength arr |> ignore) + + [] + member _.RandomChoicesBy() = + let arr = [| 1..50 |] + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choicesLength = 20 + let choice1 = arr |> Array.randomChoicesBy rand1.NextDouble choicesLength + let choice2 = arr |> Array.randomChoicesBy rand2.NextDouble choicesLength + let choice3 = arr |> Array.randomChoicesBy rand3.NextDouble choicesLength + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + + [] + member _.RandomChoicesByWrongArg() = + let nullArr = null + let emptyArr = [||] + let arr = [| 1..50 |] + let wrongRandomizers = [ + fun () -> nan + fun () -> 1.0 + fun () -> infinity + ] + let randomizer = Random(123).NextDouble + let choicesLength = 20 + let negativeChoicesLength = -1 + + CheckThrowsArgumentNullException (fun () -> Array.randomChoicesBy randomizer choicesLength nullArr |> ignore) + wrongRandomizers |> List.iter (fun wrongRandomizer -> + CheckThrowsArgumentOutOfRangeException (fun () -> Array.randomChoicesBy wrongRandomizer choicesLength arr |> ignore)) + CheckThrowsArgumentException (fun () -> Array.randomChoicesBy randomizer choicesLength emptyArr |> ignore) + CheckThrowsArgumentException (fun () -> Array.randomChoicesBy randomizer negativeChoicesLength arr |> ignore) + + [] + member _.RandomSample() = + let arr = [| 1..50 |] + + let choicesLength = 20 + let choice1 = arr |> Array.randomSample choicesLength + let choice2 = arr |> Array.randomSample choicesLength + + Assert.AreNotEqual(choice1, choice2) + Assert.AreEqual(choicesLength, choice1.Length) + Assert.AreEqual(choicesLength, choice2.Length) + Assert.AreEqual(choice1, choice1 |> Array.distinct) + Assert.AreEqual(choice2, choice2 |> Array.distinct) + + [] + member _.RandomSampleWrongArg() = + let nullArr = null + let emptyArr = [||] + let arr = [| 1..50 |] + let tooBigSampleLength = 100 + let negativeSampleLength = -1 + let sampleLength = 20 + + CheckThrowsArgumentNullException (fun () -> Array.randomSample sampleLength nullArr |> ignore) + CheckThrowsArgumentException (fun () -> Array.randomSample sampleLength emptyArr |> ignore) + CheckThrowsArgumentException (fun () -> Array.randomSample negativeSampleLength arr |> ignore) + CheckThrowsArgumentException (fun () -> Array.randomSample tooBigSampleLength arr |> ignore) + + [] + member _.RandomSampleWith() = + let arr = [| 1..50 |] + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choicesLength = 20 + let choice1 = arr |> Array.randomSampleWith rand1 choicesLength + let choice2 = arr |> Array.randomSampleWith rand2 choicesLength + let choice3 = arr |> Array.randomSampleWith rand3 choicesLength + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + Assert.AreEqual(choicesLength, choice1.Length) + Assert.AreEqual(choicesLength, choice3.Length) + Assert.AreEqual(choice1, choice1 |> Array.distinct) + Assert.AreEqual(choice3, choice3 |> Array.distinct) + + [] + member _.RandomSampleWithWrongArg() = + let nullArr = null + let emptyArr = [||] + let arr = [| 1..50 |] + let nullRand = null + let rand = Random(123) + let tooBigSampleLength = 100 + let negativeSampleLength = -1 + let sampleLength = 20 + + CheckThrowsArgumentNullException (fun () -> Array.randomSampleWith rand sampleLength nullArr |> ignore) + CheckThrowsArgumentNullException (fun () -> Array.randomSampleWith nullRand sampleLength arr |> ignore) + CheckThrowsArgumentException (fun () -> Array.randomSampleWith rand sampleLength emptyArr |> ignore) + CheckThrowsArgumentException (fun () -> Array.randomSampleWith rand negativeSampleLength arr |> ignore) + CheckThrowsArgumentException (fun () -> Array.randomSampleWith rand tooBigSampleLength arr |> ignore) + + [] + member _.RandomSampleBy() = + let arr = [| 1..50 |] + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choicesLength = 20 + let choice1 = arr |> Array.randomSampleBy rand1.NextDouble choicesLength + let choice2 = arr |> Array.randomSampleBy rand2.NextDouble choicesLength + let choice3 = arr |> Array.randomSampleBy rand3.NextDouble choicesLength + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + Assert.AreEqual(choicesLength, choice1.Length) + Assert.AreEqual(choicesLength, choice3.Length) + Assert.AreEqual(choice1, choice1 |> Array.distinct) + Assert.AreEqual(choice3, choice3 |> Array.distinct) + + [] + member _.RandomSampleByWrongArg() = + let nullArr = null + let emptyArr = [||] + let arr = [| 1..50 |] + let wrongRandomizers = [ + fun () -> nan + fun () -> 1.0 + fun () -> infinity + ] + let randomizer = Random(123).NextDouble + let tooBigSampleLength = 100 + let negativeSampleLength = -1 + let sampleLength = 20 + + CheckThrowsArgumentNullException (fun () -> Array.randomSampleBy randomizer sampleLength nullArr |> ignore) + wrongRandomizers |> List.iter (fun wrongRandomizer -> + CheckThrowsArgumentOutOfRangeException (fun () -> Array.randomSampleBy wrongRandomizer sampleLength arr |> ignore)) + CheckThrowsArgumentException (fun () -> Array.randomSampleBy randomizer sampleLength emptyArr |> ignore) + CheckThrowsArgumentException (fun () -> Array.randomSampleBy randomizer negativeSampleLength arr |> ignore) + CheckThrowsArgumentException (fun () -> Array.randomSampleBy randomizer tooBigSampleLength arr |> ignore) \ No newline at end of file diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ListModule.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ListModule.fs index fa245c76c87..9f3ae062d02 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ListModule.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ListModule.fs @@ -1086,4 +1086,306 @@ type ListModule() = member _.``Get item with reverse index behaves as expected``() = let list = [1;2;3;4;5] - Assert.AreEqual(list.[^1], 4) \ No newline at end of file + Assert.AreEqual(list.[^1], 4) + + [] + member _.RandomShuffle() = + let list = [ 1..20 ] + + let shuffled1 = list |> List.randomShuffle + let shuffled2 = list |> List.randomShuffle + + Assert.AreNotEqual(shuffled1, list) + Assert.AreNotEqual(shuffled1, shuffled2) + + [] + member _.RandomShuffleWith() = + let arr = [ 1..20 ] + + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let shuffle1 = arr |> List.randomShuffleWith rand1 + let shuffle2 = arr |> List.randomShuffleWith rand2 + let shuffle3 = arr |> List.randomShuffleWith rand3 + + Assert.AreEqual(shuffle1, shuffle2) + Assert.AreNotEqual(arr, shuffle1) + Assert.AreNotEqual(shuffle1, shuffle3) + + [] + member _.RandomShuffleWithWrongArg() = + let list = [ 1..20 ] + let nullRand = null + + CheckThrowsArgumentNullException (fun () -> List.randomShuffleWith nullRand list |> ignore) + + [] + member _.RandomShuffleBy() = + let arr = [ 1..20 ] + + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let shuffle1 = arr |> List.randomShuffleBy rand1.NextDouble + let shuffle2 = arr |> List.randomShuffleBy rand2.NextDouble + let shuffle3 = arr |> List.randomShuffleBy rand3.NextDouble + + Assert.AreEqual(shuffle1, shuffle2) + Assert.AreNotEqual(arr, shuffle1) + Assert.AreNotEqual(shuffle1, shuffle3) + + [] + member _.RandomShuffleByWrongArg() = + let list = [ 1..20 ] + let wrongRandomizer = fun () -> 1.0 + + CheckThrowsArgumentOutOfRangeException (fun () -> List.randomShuffleBy wrongRandomizer list |> ignore) + + [] + member _.RandomChoice() = + let list = [ 1..5000 ] + + // try choice five times, if all are same, it must be broken + let results = [ + List.randomChoice list + List.randomChoice list + List.randomChoice list + List.randomChoice list + List.randomChoice list + ] + let allSame = results |> List.forall (fun x -> x = results.Head) + Assert.False(allSame) + + [] + member _.RandomChoiceWrongArg() = + let emptyList = [] + + CheckThrowsArgumentException (fun () -> List.randomChoice emptyList |> ignore) + + [] + member _.RandomChoiceWith() = + let list = [ 1..5000 ] + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choice1 = list |> List.randomChoiceWith rand1 + let choice2 = list |> List.randomChoiceWith rand2 + let choice3 = list |> List.randomChoiceWith rand3 + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + + [] + member _.RandomChoiceWithWrongArg() = + let emptyList = [] + let list = [ 1..20 ] + let nullRand = null + let rand = Random(123) + + CheckThrowsArgumentNullException (fun () -> List.randomChoiceWith nullRand list |> ignore) + CheckThrowsArgumentException (fun () -> List.randomChoiceWith rand emptyList |> ignore) + + [] + member _.RandomChoiceBy() = + let list = [ 1..5000 ] + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choice1 = list |> List.randomChoiceBy rand1.NextDouble + let choice2 = list |> List.randomChoiceBy rand2.NextDouble + let choice3 = list |> List.randomChoiceBy rand3.NextDouble + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + + [] + member _.RandomChoiceByWrongArg() = + let emptyList = [] + let list = [ 1..20 ] + let wrongRandomizer = fun () -> 1.0 + let randomizer = Random(123).NextDouble + + CheckThrowsArgumentOutOfRangeException (fun () -> List.randomChoiceBy wrongRandomizer list |> ignore) + CheckThrowsArgumentException (fun () -> List.randomChoiceBy randomizer emptyList |> ignore) + + [] + member _.RandomChoices() = + let list = [ 1..50 ] + + let choicesLength = 20 + let choice1 = list |> List.randomChoices choicesLength + let choice2 = list |> List.randomChoices choicesLength + + Assert.AreNotEqual(choice1, choice2) + Assert.AreEqual(choicesLength, choice1.Length) + Assert.AreEqual(choicesLength, choice2.Length) + + let list = [ 1; 2 ] + let choices = list |> List.randomChoices choicesLength + Assert.AreEqual(choicesLength, choices.Length) + Assert.AreEqual(list, choices |> List.distinct |> List.sort) + + [] + member _.RandomChoicesWrongArg() = + let emptyList = [] + let list = [ 1..50 ] + let choicesLength = 20 + let negativeChoicesLength = -1 + + CheckThrowsArgumentException (fun () -> List.randomChoices choicesLength emptyList |> ignore) + CheckThrowsArgumentException (fun () -> List.randomChoices negativeChoicesLength list |> ignore) + + [] + member _.RandomChoicesWith() = + let list = [ 1..50 ] + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choicesLength = 20 + let choice1 = list |> List.randomChoicesWith rand1 choicesLength + let choice2 = list |> List.randomChoicesWith rand2 choicesLength + let choice3 = list |> List.randomChoicesWith rand3 choicesLength + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + + [] + member _.RandomChoicesWithWrongArg() = + let emptyList = [] + let list = [ 1..50 ] + let nullRand = null + let rand = Random(123) + let choicesLength = 20 + let negativeChoicesLength = -1 + + CheckThrowsArgumentNullException (fun () -> List.randomChoicesWith nullRand choicesLength list |> ignore) + CheckThrowsArgumentException (fun () -> List.randomChoicesWith rand choicesLength emptyList |> ignore) + CheckThrowsArgumentException (fun () -> List.randomChoicesWith rand negativeChoicesLength list |> ignore) + + [] + member _.RandomChoicesBy() = + let list = [ 1..50 ] + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choicesLength = 20 + let choice1 = list |> List.randomChoicesBy rand1.NextDouble choicesLength + let choice2 = list |> List.randomChoicesBy rand2.NextDouble choicesLength + let choice3 = list |> List.randomChoicesBy rand3.NextDouble choicesLength + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + + [] + member _.RandomChoicesByWrongArg() = + let emptyList = [] + let list = [ 1..50 ] + let wrongRandomizer = fun () -> 1.0 + let randomizer = Random(123).NextDouble + let choicesLength = 20 + let negativeChoicesLength = -1 + + CheckThrowsArgumentOutOfRangeException (fun () -> List.randomChoicesBy wrongRandomizer choicesLength list |> ignore) + CheckThrowsArgumentException (fun () -> List.randomChoicesBy randomizer choicesLength emptyList |> ignore) + CheckThrowsArgumentException (fun () -> List.randomChoicesBy randomizer negativeChoicesLength list |> ignore) + + [] + member _.RandomSample() = + let arr = [ 1..50 ] + + let choicesLength = 20 + let choice1 = arr |> List.randomSample choicesLength + let choice2 = arr |> List.randomSample choicesLength + + Assert.AreNotEqual(choice1, choice2) + Assert.AreEqual(choicesLength, choice1.Length) + Assert.AreEqual(choicesLength, choice2.Length) + Assert.AreEqual(choice1, choice1 |> List.distinct) + Assert.AreEqual(choice2, choice2 |> List.distinct) + + [] + member _.RandomSampleWrongArg() = + let emptyList = [] + let list = [ 1..50 ] + let tooBigSampleLength = 100 + let negativeSampleLength = -1 + let sampleLength = 20 + + CheckThrowsArgumentException (fun () -> List.randomSample sampleLength emptyList |> ignore) + CheckThrowsArgumentException (fun () -> List.randomSample negativeSampleLength list |> ignore) + CheckThrowsArgumentException (fun () -> List.randomSample tooBigSampleLength list |> ignore) + + [] + member _.RandomSampleWith() = + let list = [ 1..50 ] + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choicesLength = 20 + let choice1 = list |> List.randomSampleWith rand1 choicesLength + let choice2 = list |> List.randomSampleWith rand2 choicesLength + let choice3 = list |> List.randomSampleWith rand3 choicesLength + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + Assert.AreEqual(choicesLength, choice1.Length) + Assert.AreEqual(choicesLength, choice3.Length) + Assert.AreEqual(choice1, choice1 |> List.distinct) + Assert.AreEqual(choice3, choice3 |> List.distinct) + + [] + member _.RandomSampleWithWrongArg() = + let emptyArr = [] + let list = [ 1..50 ] + let nullRand = null + let rand = Random(123) + let tooBigSampleLength = 100 + let negativeSampleLength = -1 + let sampleLength = 20 + + CheckThrowsArgumentNullException (fun () -> List.randomSampleWith nullRand sampleLength list |> ignore) + CheckThrowsArgumentException (fun () -> List.randomSampleWith rand sampleLength emptyArr |> ignore) + CheckThrowsArgumentException (fun () -> List.randomSampleWith rand negativeSampleLength list |> ignore) + CheckThrowsArgumentException (fun () -> List.randomSampleWith rand tooBigSampleLength list |> ignore) + + [] + member _.RandomSampleBy() = + let list = [ 1..50 ] + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choicesLength = 20 + let choice1 = list |> List.randomSampleBy rand1.NextDouble choicesLength + let choice2 = list |> List.randomSampleBy rand2.NextDouble choicesLength + let choice3 = list |> List.randomSampleBy rand3.NextDouble choicesLength + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + Assert.AreEqual(choicesLength, choice1.Length) + Assert.AreEqual(choicesLength, choice3.Length) + Assert.AreEqual(choice1, choice1 |> List.distinct) + Assert.AreEqual(choice3, choice3 |> List.distinct) + + [] + member _.RandomSampleByWrongArg() = + let emptyArr = [] + let list = [ 1..50 ] + let wrongRandomizer = fun () -> 1.0 + let randomizer = Random(123).NextDouble + let tooBigSampleLength = 100 + let negativeSampleLength = -1 + let sampleLength = 20 + + CheckThrowsArgumentOutOfRangeException (fun () -> List.randomSampleBy wrongRandomizer sampleLength list |> ignore) + CheckThrowsArgumentException (fun () -> List.randomSampleBy randomizer sampleLength emptyArr |> ignore) + CheckThrowsArgumentException (fun () -> List.randomSampleBy randomizer negativeSampleLength list |> ignore) + CheckThrowsArgumentException (fun () -> List.randomSampleBy randomizer tooBigSampleLength list |> ignore) \ No newline at end of file diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/SeqModule.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/SeqModule.fs index d80f5404a53..7083ada323c 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/SeqModule.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/SeqModule.fs @@ -1160,3 +1160,335 @@ type SeqModule() = let nullSeq:seq<'a> = null CheckThrowsArgumentNullException (fun () -> Seq.contains 5 nullSeq |> ignore) + + [] + member _.RandomShuffle() = + let intSeq = seq { 1..20 } + + let shuffled1 = intSeq |> Seq.randomShuffle |> Seq.cache + let shuffled2 = intSeq |> Seq.randomShuffle |> Seq.cache + + Assert.AreNotEqual(shuffled1, intSeq) + Assert.AreNotEqual(shuffled1, shuffled2) + + [] + member _.RandomShuffleWrongArg() = + let nullSeq = null + CheckThrowsArgumentNullException (fun () -> Seq.randomShuffle nullSeq |> ignore) + + [] + member _.RandomShuffleWith() = + let intSeq = seq { 1..20 } + + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let shuffle1 = intSeq |> Seq.randomShuffleWith rand1 |> Seq.cache + let shuffle2 = intSeq |> Seq.randomShuffleWith rand2 |> Seq.cache + let shuffle3 = intSeq |> Seq.randomShuffleWith rand3 |> Seq.cache + + Assert.AreEqual(shuffle1, shuffle2) + Assert.AreNotEqual(intSeq, shuffle1) + Assert.AreNotEqual(shuffle1, shuffle3) + + [] + member _.RandomShuffleWithWrongArg() = + let nullSeq = null + let intSeq = seq { 1..20 } + let nullRand = null + let rand = Random(123) + + CheckThrowsArgumentNullException (fun () -> Seq.randomShuffleWith rand nullSeq |> ignore) + CheckThrowsArgumentNullException (fun () -> Seq.randomShuffleWith nullRand intSeq |> ignore) + + [] + member _.RandomShuffleBy() = + let intSeq = seq { 1..20 } + + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let shuffle1 = intSeq |> Seq.randomShuffleBy rand1.NextDouble |> Seq.cache + let shuffle2 = intSeq |> Seq.randomShuffleBy rand2.NextDouble |> Seq.cache + let shuffle3 = intSeq |> Seq.randomShuffleBy rand3.NextDouble |> Seq.cache + + Assert.AreEqual(shuffle1, shuffle2) + Assert.AreNotEqual(intSeq, shuffle1) + Assert.AreNotEqual(shuffle1, shuffle3) + + [] + member _.RandomShuffleByWrongArg() = + let nullSeq = null + let intSeq = seq { 1..20 } + let wrongRandomizer = fun () -> 1.0 + let randomizer = Random(123).NextDouble + + CheckThrowsArgumentNullException (fun () -> Seq.randomShuffleBy randomizer nullSeq |> ignore) + CheckThrowsArgumentOutOfRangeException (fun () -> Seq.randomShuffleBy wrongRandomizer intSeq |> ignore) + + [] + member _.RandomChoice() = + let intSeq = seq { 1..5000 } + + // try choice five times, if all are same, it must be broken + let results = [| + Seq.randomChoice intSeq + Seq.randomChoice intSeq + Seq.randomChoice intSeq + Seq.randomChoice intSeq + Seq.randomChoice intSeq + |] + let allSame = results |> Array.forall (fun x -> x = results.[0]) + Assert.False(allSame) + + [] + member _.RandomChoiceWrongArg() = + let nullSeq = null + let emptySeq = Seq.empty + + CheckThrowsArgumentNullException (fun () -> Seq.randomChoice nullSeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomChoice emptySeq |> ignore) + + [] + member _.RandomChoiceWith() = + let intSeq = seq { 1..5000 } + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choice1 = intSeq |> Seq.randomChoiceWith rand1 + let choice2 = intSeq |> Seq.randomChoiceWith rand2 + let choice3 = intSeq |> Seq.randomChoiceWith rand3 + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + + [] + member _.RandomChoiceWithWrongArg() = + let nullSeq = null + let emptySeq = Seq.empty + let intSeq = seq { 1..20 } + let nullRand = null + let rand = Random(123) + + CheckThrowsArgumentNullException (fun () -> Seq.randomChoiceWith rand nullSeq |> ignore) + CheckThrowsArgumentNullException (fun () -> Seq.randomChoiceWith nullRand intSeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomChoiceWith rand emptySeq |> ignore) + + [] + member _.RandomChoiceBy() = + let intSeq = seq { 1..5000 } + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choice1 = intSeq |> Seq.randomChoiceBy rand1.NextDouble + let choice2 = intSeq |> Seq.randomChoiceBy rand2.NextDouble + let choice3 = intSeq |> Seq.randomChoiceBy rand3.NextDouble + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + + [] + member _.RandomChoiceByWrongArg() = + let nullSeq = null + let emptySeq = Seq.empty + let intSeq = seq { 1..20 } + let wrongRandomizer = fun () -> 1.0 + let randomizer = Random(123).NextDouble + + CheckThrowsArgumentNullException (fun () -> Seq.randomChoiceBy randomizer nullSeq |> ignore) + CheckThrowsArgumentOutOfRangeException (fun () -> Seq.randomChoiceBy wrongRandomizer intSeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomChoiceBy randomizer emptySeq |> ignore) + + [] + member _.RandomChoices() = + let intSeq = seq { 1..50 } + + let choicesLength = 20 + let choice1 = intSeq |> Seq.randomChoices choicesLength |> Seq.cache + let choice2 = intSeq |> Seq.randomChoices choicesLength |> Seq.cache + + Assert.AreNotEqual(choice1, choice2) + Assert.AreEqual(choicesLength, choice1 |> Seq.length) + Assert.AreEqual(choicesLength, choice2 |> Seq.length) + + let intSeq = seq { 1; 2 } + let choices = intSeq |> Seq.randomChoices choicesLength + Assert.AreEqual(choicesLength, choices |> Seq.length) + Assert.AreEqual(intSeq, choices |> Seq.distinct |> Seq.sort) + + [] + member _.RandomChoicesWrongArg() = + let nullSeq = null + let emptySeq = Seq.empty + let intSeq = seq { 1..50 } + let choicesLength = 20 + let negativeChoicesLength = -1 + + CheckThrowsArgumentNullException (fun () -> Seq.randomChoices choicesLength nullSeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomChoices choicesLength emptySeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomChoices negativeChoicesLength intSeq |> ignore) + + [] + member _.RandomChoicesWith() = + let seq = seq { 1..50 } + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choicesLength = 20 + let choice1 = seq |> Seq.randomChoicesWith rand1 choicesLength |> Seq.cache + let choice2 = seq |> Seq.randomChoicesWith rand2 choicesLength |> Seq.cache + let choice3 = seq |> Seq.randomChoicesWith rand3 choicesLength |> Seq.cache + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + + [] + member _.RandomChoicesWithWrongArg() = + let nullSeq = null + let emptySeq = Seq.empty + let intSeq = seq { 1..50 } + let nullRand = null + let rand = Random(123) + let choicesLength = 20 + let negativeChoicesLength = -1 + + CheckThrowsArgumentNullException (fun () -> Seq.randomChoicesWith rand choicesLength nullSeq |> ignore) + CheckThrowsArgumentNullException (fun () -> Seq.randomChoicesWith nullRand choicesLength intSeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomChoicesWith rand choicesLength emptySeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomChoicesWith rand negativeChoicesLength intSeq |> ignore) + + [] + member _.RandomChoicesBy() = + let seq = seq { 1..50 } + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choicesLength = 20 + let choice1 = seq |> Seq.randomChoicesBy rand1.NextDouble choicesLength |> Seq.cache + let choice2 = seq |> Seq.randomChoicesBy rand2.NextDouble choicesLength |> Seq.cache + let choice3 = seq |> Seq.randomChoicesBy rand3.NextDouble choicesLength |> Seq.cache + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + + [] + member _.RandomChoicesByWrongArg() = + let nullSeq = null + let emptySeq = Seq.empty + let intSeq = seq { 1..50 } + let wrongRandomizer = fun () -> 1.0 + let randomizer = Random(123).NextDouble + let choicesLength = 20 + let negativeChoicesLength = -1 + + CheckThrowsArgumentNullException (fun () -> Seq.randomChoicesBy randomizer choicesLength nullSeq |> ignore) + CheckThrowsArgumentOutOfRangeException (fun () -> Seq.randomChoicesBy wrongRandomizer choicesLength intSeq |> Seq.toList |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomChoicesBy randomizer choicesLength emptySeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomChoicesBy randomizer negativeChoicesLength intSeq |> ignore) + + [] + member _.RandomSample() = + let intSeq = seq { 1..50 } + + let choicesLength = 20 + let choice1 = intSeq |> Seq.randomSample choicesLength |> Seq.cache + let choice2 = intSeq |> Seq.randomSample choicesLength |> Seq.cache + + Assert.AreNotEqual(choice1, choice2) + Assert.AreEqual(choicesLength, choice1 |> Seq.length) + Assert.AreEqual(choicesLength, choice2 |> Seq.length) + + Assert.AreEqual(choice1, choice1 |> Seq.distinct) + Assert.AreEqual(choice2, choice2 |> Seq.distinct) + + [] + member _.RandomSampleWrongArg() = + let nullSeq = null + let emptySeq = Seq.empty + let intSeq = seq { 1..50 } + let tooBigSampleLength = 100 + let negativeSampleLength = -1 + let sampleLength = 20 + + CheckThrowsArgumentNullException (fun () -> Seq.randomSample sampleLength nullSeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomSample sampleLength emptySeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomSample negativeSampleLength intSeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomSample tooBigSampleLength intSeq |> ignore) + + [] + member _.RandomSampleWith() = + let intSeq = seq { 1..50 } + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choicesLength = 20 + let choice1 = intSeq |> Seq.randomSampleWith rand1 choicesLength |> Seq.cache + let choice2 = intSeq |> Seq.randomSampleWith rand2 choicesLength |> Seq.cache + let choice3 = intSeq |> Seq.randomSampleWith rand3 choicesLength |> Seq.cache + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + Assert.AreEqual(choicesLength, choice1 |> Seq.length) + Assert.AreEqual(choicesLength, choice3 |> Seq.length) + Assert.AreEqual(choice1, choice1 |> Seq.distinct) + Assert.AreEqual(choice3, choice3 |> Seq.distinct) + + [] + member _.RandomSampleWithWrongArg() = + let nullSeq = null + let emptySeq = Seq.empty + let intSeq = seq { 1..50 } + let nullRand = null + let rand = Random(123) + let tooBigSampleLength = 100 + let negativeSampleLength = -1 + let sampleLength = 20 + + CheckThrowsArgumentNullException (fun () -> Seq.randomSampleWith rand sampleLength nullSeq |> ignore) + CheckThrowsArgumentNullException (fun () -> Seq.randomSampleWith nullRand sampleLength intSeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomSampleWith rand sampleLength emptySeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomSampleWith rand negativeSampleLength intSeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomSampleWith rand tooBigSampleLength intSeq |> ignore) + + [] + member _.RandomSampleBy() = + let intSeq = seq { 1..50 } + let rand1 = Random(123) + let rand2 = Random(123) + let rand3 = Random(321) + + let choicesLength = 20 + let choice1 = intSeq |> Seq.randomSampleBy rand1.NextDouble choicesLength |> Seq.cache + let choice2 = intSeq |> Seq.randomSampleBy rand2.NextDouble choicesLength |> Seq.cache + let choice3 = intSeq |> Seq.randomSampleBy rand3.NextDouble choicesLength |> Seq.cache + + Assert.AreEqual(choice1, choice2) + Assert.AreNotEqual(choice1, choice3) + Assert.AreEqual(choicesLength, choice1 |> Seq.length) + Assert.AreEqual(choicesLength, choice3 |> Seq.length) + Assert.AreEqual(choice1, choice1 |> Seq.distinct) + Assert.AreEqual(choice3, choice3 |> Seq.distinct) + + [] + member _.RandomSampleByWrongArg() = + let nullSeq = null + let emptySeq = Seq.empty + let intSeq = seq { 1..50 } + let wrongRandomizer = fun () -> 1.0 + let randomizer = Random(123).NextDouble + let tooBigSampleLength = 100 + let negativeSampleLength = -1 + let sampleLength = 20 + + CheckThrowsArgumentNullException (fun () -> Seq.randomSampleBy randomizer sampleLength nullSeq |> ignore) + CheckThrowsArgumentOutOfRangeException (fun () -> Seq.randomSampleBy wrongRandomizer sampleLength intSeq |> Seq.toList |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomSampleBy randomizer sampleLength emptySeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomSampleBy randomizer negativeSampleLength intSeq |> ignore) + CheckThrowsArgumentException (fun () -> Seq.randomSampleBy randomizer tooBigSampleLength intSeq |> ignore) \ No newline at end of file From db21cf2597d00dac2b50e0f1ffaa480c8b32283f Mon Sep 17 00:00:00 2001 From: Alex Berezhnykh Date: Wed, 26 Jun 2024 19:16:04 +0300 Subject: [PATCH 2/4] Optimize metadata reading for extension methods (#16168) Co-authored-by: Petr --- .../.FSharp.Compiler.Service/8.0.400.md | 1 + src/Compiler/AbstractIL/il.fs | 42 +++++-- src/Compiler/AbstractIL/il.fsi | 20 ++- src/Compiler/AbstractIL/ilmorph.fs | 2 +- src/Compiler/AbstractIL/ilread.fs | 117 +++++++++++++++--- src/Compiler/Checking/NameResolution.fs | 5 +- src/Compiler/CodeGen/EraseClosures.fs | 8 +- src/Compiler/CodeGen/EraseUnions.fs | 4 +- src/Compiler/CodeGen/IlxGen.fs | 68 ++++++---- ...ervice.SurfaceArea.netstandard20.debug.bsl | 14 ++- ...vice.SurfaceArea.netstandard20.release.bsl | 14 ++- .../ModuleReaderCancellationTests.fs | 6 +- 12 files changed, 221 insertions(+), 80 deletions(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md index 0f491fc6808..12a54d572fc 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md +++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md @@ -35,3 +35,4 @@ * Reduce allocations in compiler checking via `ValueOption` usage ([PR #16822](https://github.com/dotnet/fsharp/pull/16822)) * Use AsyncLocal instead of ThreadStatic to hold Cancellable.Token ([PR #17156](https://github.com/dotnet/fsharp/pull/17156)) * Showing and inserting correct name of entities from unopened namespace/module ([Issue #14375](https://github.com/dotnet/fsharp/issues/14375), [PR #17261](https://github.com/dotnet/fsharp/pull/17261)) +* Support lazy custom attributes calculation for `ILTypeDef` public API, improve `ExtensionAttribute` presence detecting perf. ([PR #16168](https://github.com/dotnet/fsharp/pull/16168)) diff --git a/src/Compiler/AbstractIL/il.fs b/src/Compiler/AbstractIL/il.fs index 48cee265fdc..66b736f87c3 100644 --- a/src/Compiler/AbstractIL/il.fs +++ b/src/Compiler/AbstractIL/il.fs @@ -1258,6 +1258,9 @@ let storeILCustomAttrs (attrs: ILAttributes) = else ILAttributesStored.Given attrs +let mkILCustomAttrsComputed f = + ILAttributesStored.Reader(fun _ -> f ()) + let mkILCustomAttrsReader f = ILAttributesStored.Reader f type ILCodeLabel = int @@ -2611,6 +2614,14 @@ let convertInitSemantics (init: ILTypeInit) = | ILTypeInit.BeforeField -> TypeAttributes.BeforeFieldInit | ILTypeInit.OnAny -> enum 0 +[] +type ILTypeDefAdditionalFlags = + | None = 0 + | IsKnownToBeAttribute = 1 + /// The type can contain extension methods, + /// or this information may not be available at the time the ILTypeDef is created + | CanContainExtensionMethods = 2 + [] type ILTypeDef ( @@ -2626,7 +2637,7 @@ type ILTypeDef methodImpls: ILMethodImplDefs, events: ILEventDefs, properties: ILPropertyDefs, - isKnownToBeAttribute: bool, + additionalFlags: ILTypeDefAdditionalFlags, securityDeclsStored: ILSecurityDeclsStored, customAttrsStored: ILAttributesStored, metadataIndex: int32 @@ -2634,6 +2645,8 @@ type ILTypeDef let mutable customAttrsStored = customAttrsStored + let hasFlag flag = additionalFlags &&& flag = flag + new(name, attributes, layout, @@ -2646,7 +2659,7 @@ type ILTypeDef methodImpls, events, properties, - isKnownToBeAttribute, + additionalFlags, securityDecls, customAttrs) = ILTypeDef( @@ -2662,9 +2675,9 @@ type ILTypeDef methodImpls, events, properties, - isKnownToBeAttribute, + additionalFlags, storeILSecurityDecls securityDecls, - storeILCustomAttrs customAttrs, + customAttrs, NoMetadataIdx ) @@ -2694,7 +2707,10 @@ type ILTypeDef member _.Properties = properties - member _.IsKnownToBeAttribute = isKnownToBeAttribute + member _.IsKnownToBeAttribute = hasFlag ILTypeDefAdditionalFlags.IsKnownToBeAttribute + + member _.CanContainExtensionMethods = + hasFlag ILTypeDefAdditionalFlags.CanContainExtensionMethods member _.CustomAttrsStored = customAttrsStored @@ -2714,7 +2730,7 @@ type ILTypeDef ?methodImpls, ?events, ?properties, - ?isKnownToBeAttribute, + ?newAdditionalFlags, ?customAttrs, ?securityDecls ) = @@ -2732,11 +2748,11 @@ type ILTypeDef methodImpls = defaultArg methodImpls x.MethodImpls, events = defaultArg events x.Events, properties = defaultArg properties x.Properties, - isKnownToBeAttribute = defaultArg isKnownToBeAttribute x.IsKnownToBeAttribute, - customAttrs = defaultArg customAttrs x.CustomAttrs + additionalFlags = defaultArg newAdditionalFlags additionalFlags, + customAttrs = defaultArg customAttrs (storeILCustomAttrs x.CustomAttrs) ) - member x.CustomAttrs = + member x.CustomAttrs: ILAttributes = match customAttrsStored with | ILAttributesStored.Reader f -> let res = ILAttributes(f x.MetadataIndex) @@ -4220,11 +4236,11 @@ let mkILGenericClass (nm, access, genparams, extends, impl, methods, fields, nes methods = methods, fields = fields, nestedTypes = nestedTypes, - customAttrs = attrs, + customAttrs = storeILCustomAttrs attrs, methodImpls = emptyILMethodImpls, properties = props, events = events, - isKnownToBeAttribute = false, + additionalFlags = ILTypeDefAdditionalFlags.None, securityDecls = emptyILSecurityDecls ) @@ -4244,11 +4260,11 @@ let mkRawDataValueTypeDef (iltyp_ValueType: ILType) (nm, size, pack) = methods = emptyILMethods, fields = emptyILFields, nestedTypes = emptyILTypeDefs, - customAttrs = emptyILCustomAttrs, + customAttrs = emptyILCustomAttrsStored, methodImpls = emptyILMethodImpls, properties = emptyILProperties, events = emptyILEvents, - isKnownToBeAttribute = false, + additionalFlags = ILTypeDefAdditionalFlags.None, securityDecls = emptyILSecurityDecls ) diff --git a/src/Compiler/AbstractIL/il.fsi b/src/Compiler/AbstractIL/il.fsi index 5e02f4c0c1e..d6673131060 100644 --- a/src/Compiler/AbstractIL/il.fsi +++ b/src/Compiler/AbstractIL/il.fsi @@ -4,6 +4,7 @@ module rec FSharp.Compiler.AbstractIL.IL +open System open FSharp.Compiler.IO open System.Collections.Generic open System.Reflection @@ -1481,6 +1482,12 @@ type ILTypeDefs = /// Calls to ExistsByName will result in all the ILPreTypeDefs being read. member internal ExistsByName: string -> bool +[] +type ILTypeDefAdditionalFlags = + | None = 0 + | IsKnownToBeAttribute = 1 + | CanContainExtensionMethods = 2 + /// Represents IL Type Definitions. [] type ILTypeDef = @@ -1499,7 +1506,7 @@ type ILTypeDef = methodImpls: ILMethodImplDefs * events: ILEventDefs * properties: ILPropertyDefs * - isKnownToBeAttribute: bool * + additionalFlags: ILTypeDefAdditionalFlags * securityDeclsStored: ILSecurityDeclsStored * customAttrsStored: ILAttributesStored * metadataIndex: int32 -> @@ -1519,9 +1526,9 @@ type ILTypeDef = methodImpls: ILMethodImplDefs * events: ILEventDefs * properties: ILPropertyDefs * - isKnownToBeAttribute: bool * + additionalFlags: ILTypeDefAdditionalFlags * securityDecls: ILSecurityDecls * - customAttrs: ILAttributes -> + customAttrs: ILAttributesStored -> ILTypeDef member Name: string @@ -1556,6 +1563,7 @@ type ILTypeDef = member HasSecurity: bool member Encoding: ILDefaultPInvokeEncoding member IsKnownToBeAttribute: bool + member CanContainExtensionMethods: bool member internal WithAccess: ILTypeDefAccess -> ILTypeDef member internal WithNestedAccess: ILMemberAccess -> ILTypeDef @@ -1584,8 +1592,8 @@ type ILTypeDef = ?methodImpls: ILMethodImplDefs * ?events: ILEventDefs * ?properties: ILPropertyDefs * - ?isKnownToBeAttribute: bool * - ?customAttrs: ILAttributes * + ?newAdditionalFlags: ILTypeDefAdditionalFlags * + ?customAttrs: ILAttributesStored * ?securityDecls: ILSecurityDecls -> ILTypeDef @@ -2212,8 +2220,10 @@ val internal mkILTypeForGlobalFunctions: ILScopeRef -> ILType val mkILCustomAttrs: ILAttribute list -> ILAttributes val mkILCustomAttrsFromArray: ILAttribute[] -> ILAttributes val storeILCustomAttrs: ILAttributes -> ILAttributesStored +val mkILCustomAttrsComputed: (unit -> ILAttribute[]) -> ILAttributesStored val internal mkILCustomAttrsReader: (int32 -> ILAttribute[]) -> ILAttributesStored val emptyILCustomAttrs: ILAttributes +val emptyILCustomAttrsStored: ILAttributesStored val mkILSecurityDecls: ILSecurityDecl list -> ILSecurityDecls val emptyILSecurityDecls: ILSecurityDecls diff --git a/src/Compiler/AbstractIL/ilmorph.fs b/src/Compiler/AbstractIL/ilmorph.fs index b4305791076..334ed93d212 100644 --- a/src/Compiler/AbstractIL/ilmorph.fs +++ b/src/Compiler/AbstractIL/ilmorph.fs @@ -378,7 +378,7 @@ let rec tdef_ty2ty_ilmbody2ilmbody_mdefs2mdefs isInKnownSet enc fs (tdef: ILType methodImpls = mimpls_ty2ty fTyInCtxtR tdef.MethodImpls, events = edefs_ty2ty fTyInCtxtR tdef.Events, properties = pdefs_ty2ty fTyInCtxtR tdef.Properties, - customAttrs = cattrs_ty2ty fTyInCtxtR tdef.CustomAttrs + customAttrs = storeILCustomAttrs (cattrs_ty2ty fTyInCtxtR tdef.CustomAttrs) ) and tdefs_ty2ty_ilmbody2ilmbody_mdefs2mdefs isInKnownSet enc fs tdefs = diff --git a/src/Compiler/AbstractIL/ilread.fs b/src/Compiler/AbstractIL/ilread.fs index 61c04f8e4ba..1535078bfe0 100644 --- a/src/Compiler/AbstractIL/ilread.fs +++ b/src/Compiler/AbstractIL/ilread.fs @@ -858,10 +858,10 @@ let hsCompare (TaggedIndex(t1: HasSemanticsTag, idx1: int)) (TaggedIndex(t2: Has elif idx1 > idx2 then 1 else compare t1.Tag t2.Tag -let hcaCompare (TaggedIndex(t1: HasCustomAttributeTag, idx1: int)) (TaggedIndex(t2: HasCustomAttributeTag, idx2)) = - if idx1 < idx2 then -1 - elif idx1 > idx2 then 1 - else compare t1.Tag t2.Tag +let inline hcaCompare (t1: TaggedIndex) (t2: TaggedIndex) = + if t1.index < t2.index then -1 + elif t1.index > t2.index then 1 + else compare t1.tag t2.tag let mfCompare (TaggedIndex(t1: MemberForwardedTag, idx1: int)) (TaggedIndex(t2: MemberForwardedTag, idx2)) = if idx1 < idx2 then -1 @@ -2112,9 +2112,82 @@ and typeDefReader ctxtH : ILTypeDefStored = let layout = typeLayoutOfFlags ctxt mdv flags idx let hasLayout = - (match layout with - | ILTypeDefLayout.Explicit _ -> true - | _ -> false) + match layout with + | ILTypeDefLayout.Explicit _ -> true + | _ -> false + + let containsExtensionMethods = + let mutable containsExtensionMethods = false + let searchedKey = TaggedIndex(hca_TypeDef, idx) + + let attributesSearcher = + { new ISeekReadIndexedRowReader with + member _.GetRow(i, rowIndex) = rowIndex <- i + member _.GetKey(rowIndex) = rowIndex + + member _.CompareKey(rowIndex) = + let mutable addr = ctxt.rowAddr TableNames.CustomAttribute rowIndex + // read parentIndex + let key = seekReadHasCustomAttributeIdx ctxt mdv &addr + hcaCompare searchedKey key + + member _.ConvertRow(i) = i + } + + let attrsStartIdx, attrsEndIdx = + seekReadIndexedRowsRange + (ctxt.getNumRows TableNames.CustomAttribute) + (isSorted ctxt TableNames.CustomAttribute) + attributesSearcher + + if attrsStartIdx <= 0 || attrsEndIdx < attrsStartIdx then + false + else + let mutable attrIdx = attrsStartIdx + + let looksLikeSystemAssembly = + ctxt.fileName.EndsWith("System.Runtime.dll") + || ctxt.fileName.EndsWith("mscorlib.dll") + || ctxt.fileName.EndsWith("netstandard.dll") + + while attrIdx <= attrsEndIdx && not containsExtensionMethods do + let mutable addr = ctxt.rowAddr TableNames.CustomAttribute attrIdx + // skip parentIndex to read typeIndex + seekReadHasCustomAttributeIdx ctxt mdv &addr |> ignore + let attrTypeIndex = seekReadCustomAttributeTypeIdx ctxt mdv &addr + let attrCtorIdx = attrTypeIndex.index + + let name = + if attrTypeIndex.tag = cat_MethodDef then + // the ExtensionAttribute constructor can be cat_MethodDef if the metadata is read from the assembly + // in which the corresponding attribute is defined -- from the system library + if not looksLikeSystemAssembly then + "" + else + let _, (_, nameIdx, namespaceIdx, _, _, _) = seekMethodDefParent ctxt attrCtorIdx + readBlobHeapAsTypeName ctxt (nameIdx, namespaceIdx) + else + let mutable addr = ctxt.rowAddr TableNames.MemberRef attrCtorIdx + let mrpTag = seekReadMemberRefParentIdx ctxt mdv &addr + + if mrpTag.tag <> mrp_TypeRef then + "" + else + let _, nameIdx, namespaceIdx = seekReadTypeRefRow ctxt mdv mrpTag.index + readBlobHeapAsTypeName ctxt (nameIdx, namespaceIdx) + + if name = "System.Runtime.CompilerServices.ExtensionAttribute" then + containsExtensionMethods <- true + + attrIdx <- attrIdx + 1 + + containsExtensionMethods + + let additionalFlags = + if containsExtensionMethods then + ILTypeDefAdditionalFlags.CanContainExtensionMethods + else + ILTypeDefAdditionalFlags.None let mdefs = seekReadMethods ctxt numTypars methodsIdx endMethodsIdx let fdefs = seekReadFields ctxt (numTypars, hasLayout) fieldsIdx endFieldsIdx @@ -2138,7 +2211,7 @@ and typeDefReader ctxtH : ILTypeDefStored = methodImpls = mimpls, events = events, properties = props, - isKnownToBeAttribute = false, + additionalFlags = additionalFlags, customAttrsStored = ctxt.customAttrsReader_TypeDef, metadataIndex = idx )) @@ -2797,22 +2870,26 @@ and seekReadMemberRefAsFieldSpecUncached ctxtH (MemberRefAsFspecIdx(numTypars, i // method-range and field-range start/finish indexes and seekReadMethodDefAsMethodData ctxt idx = ctxt.seekReadMethodDefAsMethodData idx +and seekMethodDefParent (ctxt: ILMetadataReader) methodIdx = + seekReadIndexedRow ( + ctxt.getNumRows TableNames.TypeDef, + (fun i -> i, seekReadTypeDefRow ctxt i), + id, + (fun (i, (_, _, _, _, _, methodsIdx as info)) -> + if methodsIdx > methodIdx then + -1 + else + let struct (_, endMethodsIdx) = seekReadTypeDefRowExtents ctxt info i + if endMethodsIdx <= methodIdx then 1 else 0), + true, + id + ) + and seekReadMethodDefAsMethodDataUncached ctxtH idx = let (ctxt: ILMetadataReader) = getHole ctxtH let mdv = ctxt.mdfile.GetView() // Look for the method def parent. - let tidx = - seekReadIndexedRow ( - ctxt.getNumRows TableNames.TypeDef, - (fun i -> i, seekReadTypeDefRowWithExtents ctxt i), - id, - (fun (_, ((_, _, _, _, _, methodsIdx), (_, endMethodsIdx))) -> - if endMethodsIdx <= idx then 1 - elif methodsIdx <= idx && idx < endMethodsIdx then 0 - else -1), - true, - fst - ) + let tidx, _ = seekMethodDefParent ctxt idx // Create a formal instantiation if needed let typeGenericArgs = seekReadGenericParams ctxt 0 (tomd_TypeDef, tidx) let typeGenericArgsCount = typeGenericArgs.Length diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 780824e3ead..6605ae861f2 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -524,7 +524,10 @@ let NextExtensionMethodPriority() = uint64 (newStamp()) /// Checks if the type is used for C# style extension members. let IsTyconRefUsedForCSharpStyleExtensionMembers g m (tcref: TyconRef) = // Type must be non-generic and have 'Extension' attribute - isNil(tcref.Typars m) && TyconRefHasAttribute g m g.attrib_ExtensionAttribute tcref + match metadataOfTycon tcref.Deref with + | ILTypeMetadata(TILObjectReprData(_, _, tdef)) -> tdef.CanContainExtensionMethods + | _ -> true + && isNil(tcref.Typars m) && TyconRefHasAttribute g m g.attrib_ExtensionAttribute tcref /// Checks if the type is used for C# style extension members. let IsTypeUsedForCSharpStyleExtensionMembers g m ty = diff --git a/src/Compiler/CodeGen/EraseClosures.fs b/src/Compiler/CodeGen/EraseClosures.fs index cf1499f0c30..7eddfd9b820 100644 --- a/src/Compiler/CodeGen/EraseClosures.fs +++ b/src/Compiler/CodeGen/EraseClosures.fs @@ -578,11 +578,11 @@ let rec convIlxClosureDef cenv encl (td: ILTypeDef) clo = extends = Some cenv.mkILTyFuncTy, methods = mkILMethods (ctorMethodDef :: nowApplyMethDef :: nowMethods), fields = mkILFields (mkILCloFldDefs cenv nowFields @ td.Fields.AsList()), - customAttrs = emptyILCustomAttrs, + customAttrs = emptyILCustomAttrsStored, methodImpls = emptyILMethodImpls, properties = emptyILProperties, events = emptyILEvents, - isKnownToBeAttribute = false, + additionalFlags = ILTypeDefAdditionalFlags.None, securityDecls = emptyILSecurityDecls ) .WithSpecialName(false) @@ -712,11 +712,11 @@ let rec convIlxClosureDef cenv encl (td: ILTypeDef) clo = extends = Some nowEnvParentClass, methods = mkILMethods (ctorMethodDef :: nowApplyMethDef :: nowMethods), fields = mkILFields (mkILCloFldDefs cenv nowFields @ td.Fields.AsList()), - customAttrs = emptyILCustomAttrs, + customAttrs = emptyILCustomAttrsStored, methodImpls = emptyILMethodImpls, properties = emptyILProperties, events = emptyILEvents, - isKnownToBeAttribute = false, + additionalFlags = ILTypeDefAdditionalFlags.None, securityDecls = emptyILSecurityDecls ) .WithHasSecurity(false) diff --git a/src/Compiler/CodeGen/EraseUnions.fs b/src/Compiler/CodeGen/EraseUnions.fs index e21f76b3071..d5670ed3fdf 100644 --- a/src/Compiler/CodeGen/EraseUnions.fs +++ b/src/Compiler/CodeGen/EraseUnions.fs @@ -1452,8 +1452,8 @@ let mkClassUnionDef methodImpls = emptyILMethodImpls, events = emptyILEvents, properties = emptyILProperties, - isKnownToBeAttribute = false, - customAttrs = emptyILCustomAttrs + additionalFlags = ILTypeDefAdditionalFlags.None, + customAttrs = emptyILCustomAttrsStored ) .WithNestedAccess(cud.UnionCasesAccessibility) .WithAbstract(true) diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 9be96f680b7..c152a0f239a 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -6202,19 +6202,21 @@ and GenStructStateMachine cenv cgbuf eenvouter (res: LoweredStateMachine) sequel yield fdef ] + let customAttrs = + [ + g.CompilerGeneratedAttribute + mkCompilationMappingAttr g (int SourceConstructFlags.Closure) + ] + |> mkILCustomAttrs + |> storeILCustomAttrs + let cloTypeDef = ILTypeDef( name = ilCloTypeRef.Name, layout = ILTypeDefLayout.Auto, attributes = enum 0, genericParams = ilCloGenericFormals, - customAttrs = - mkILCustomAttrs ( - [ - g.CompilerGeneratedAttribute - mkCompilationMappingAttr g (int SourceConstructFlags.Closure) - ] - ), + customAttrs = customAttrs, fields = mkILFields fdefs, events = emptyILEvents, properties = emptyILProperties, @@ -6223,7 +6225,7 @@ and GenStructStateMachine cenv cgbuf eenvouter (res: LoweredStateMachine) sequel nestedTypes = emptyILTypeDefs, implements = ilInterfaceTys, extends = Some super, - isKnownToBeAttribute = false, + additionalFlags = ILTypeDefAdditionalFlags.None, securityDecls = emptyILSecurityDecls ) .WithSealed(true) @@ -6636,13 +6638,18 @@ and GenClosureTypeDefs else mdefs, [] + let customAttrs = + attrs @ [ mkCompilationMappingAttr g (int SourceConstructFlags.Closure) ] + |> mkILCustomAttrs + |> storeILCustomAttrs + let tdef = ILTypeDef( name = tref.Name, layout = ILTypeDefLayout.Auto, attributes = enum 0, genericParams = ilGenParams, - customAttrs = mkILCustomAttrs (attrs @ [ mkCompilationMappingAttr g (int SourceConstructFlags.Closure) ]), + customAttrs = customAttrs, fields = mkILFields fdefs, events = emptyILEvents, properties = emptyILProperties, @@ -6651,7 +6658,7 @@ and GenClosureTypeDefs nestedTypes = emptyILTypeDefs, implements = ilIntfTys, extends = Some ext, - isKnownToBeAttribute = false, + additionalFlags = ILTypeDefAdditionalFlags.None, securityDecls = emptyILSecurityDecls ) .WithSealed(true) @@ -11327,8 +11334,9 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option | TILObjectRepr _ -> let tdef = tycon.ILTyconRawMetadata.WithAccess tyconAccess - let tdef = - tdef.With(customAttrs = mkILCustomAttrs ilCustomAttrs, genericParams = ilGenParams) + let customAttrs = ilCustomAttrs |> mkILCustomAttrs |> storeILCustomAttrs + + let tdef = tdef.With(customAttrs = customAttrs, genericParams = ilGenParams) tdef, None @@ -11364,6 +11372,12 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option let isKnownToBeAttribute = ExistsSameHeadTypeInHierarchy g cenv.amap m super g.mk_Attribute_ty + let additionalFlags = + if isKnownToBeAttribute then + ILTypeDefAdditionalFlags.IsKnownToBeAttribute + else + ILTypeDefAdditionalFlags.None + let tdef = mkILGenericClass ( ilTypeName, @@ -11389,7 +11403,7 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option .WithSerializable(isSerializable) .WithAbstract(isAbstract) .WithImport(isComInteropTy g thisTy) - .With(methodImpls = mkILMethodImpls methodImpls, isKnownToBeAttribute = isKnownToBeAttribute) + .With(methodImpls = mkILMethodImpls methodImpls, newAdditionalFlags = additionalFlags) let tdLayout, tdEncoding = match TryFindFSharpAttribute g g.attrib_StructLayoutAttribute tycon.Attribs with @@ -11524,19 +11538,19 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option ILTypeDefLayout.Auto let cattrs = - mkILCustomAttrs ( - ilCustomAttrs - @ [ - mkCompilationMappingAttr - g - (int ( - if hiddenRepr then - SourceConstructFlags.SumType ||| SourceConstructFlags.NonPublicRepresentation - else - SourceConstructFlags.SumType - )) - ] - ) + ilCustomAttrs + @ [ + mkCompilationMappingAttr + g + (int ( + if hiddenRepr then + SourceConstructFlags.SumType ||| SourceConstructFlags.NonPublicRepresentation + else + SourceConstructFlags.SumType + )) + ] + |> mkILCustomAttrs + |> storeILCustomAttrs let tdef = ILTypeDef( @@ -11559,7 +11573,7 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) : ILTypeRef option else g.ilg.typ_Object ), - isKnownToBeAttribute = false, + additionalFlags = ILTypeDefAdditionalFlags.None, securityDecls = emptyILSecurityDecls ) .WithLayout(layout) diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl index 447ebd82026..63a16671bca 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl @@ -1483,6 +1483,7 @@ FSharp.Compiler.AbstractIL.IL+ILType: System.String QualifiedName FSharp.Compiler.AbstractIL.IL+ILType: System.String ToString() FSharp.Compiler.AbstractIL.IL+ILType: System.String get_BasicQualifiedName() FSharp.Compiler.AbstractIL.IL+ILType: System.String get_QualifiedName() +FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean CanContainExtensionMethods FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean HasSecurity FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean IsAbstract FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean IsClass @@ -1496,6 +1497,7 @@ FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean IsSerializable FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean IsSpecialName FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean IsStruct FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean IsStructOrEnum +FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean get_CanContainExtensionMethods() FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean get_HasSecurity() FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean get_IsAbstract() FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean get_IsClass() @@ -1525,7 +1527,7 @@ FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILPropertyDefs Properties FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILPropertyDefs get_Properties() FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILSecurityDecls SecurityDecls FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILSecurityDecls get_SecurityDecls() -FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILTypeDef With(Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.Reflection.TypeAttributes], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILTypeDefLayout], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILType]], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILGenericParameterDef]], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILType]], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILMethodDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILTypeDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILFieldDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILMethodImplDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILEventDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILPropertyDefs], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILAttributes], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILSecurityDecls]) +FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILTypeDef With(Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.Reflection.TypeAttributes], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILTypeDefLayout], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILType]], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILGenericParameterDef]], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILType]], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILMethodDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILTypeDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILFieldDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILMethodImplDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILEventDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILPropertyDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILTypeDefAdditionalFlags], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILAttributesStored], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILSecurityDecls]) FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILTypeDefAccess Access FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILTypeDefAccess get_Access() FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILTypeDefLayout Layout @@ -1543,7 +1545,7 @@ FSharp.Compiler.AbstractIL.IL+ILTypeDef: System.Reflection.TypeAttributes get_At FSharp.Compiler.AbstractIL.IL+ILTypeDef: System.String Name FSharp.Compiler.AbstractIL.IL+ILTypeDef: System.String ToString() FSharp.Compiler.AbstractIL.IL+ILTypeDef: System.String get_Name() -FSharp.Compiler.AbstractIL.IL+ILTypeDef: Void .ctor(System.String, System.Reflection.TypeAttributes, ILTypeDefLayout, Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILType], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILGenericParameterDef], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILType], ILMethodDefs, ILTypeDefs, ILFieldDefs, ILMethodImplDefs, ILEventDefs, ILPropertyDefs, Boolean, ILSecurityDecls, ILAttributes) +FSharp.Compiler.AbstractIL.IL+ILTypeDef: Void .ctor(System.String, System.Reflection.TypeAttributes, ILTypeDefLayout, Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILType], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILGenericParameterDef], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILType], ILMethodDefs, ILTypeDefs, ILFieldDefs, ILMethodImplDefs, ILEventDefs, ILPropertyDefs, ILTypeDefAdditionalFlags, ILSecurityDecls, ILAttributesStored) FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess+Nested: ILMemberAccess Item FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess+Nested: ILMemberAccess get_Item() FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess+Tags: Int32 Nested @@ -1574,6 +1576,10 @@ FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess: Int32 GetHashCode(System.Collecti FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess: Int32 Tag FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess: Int32 get_Tag() FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess: System.String ToString() +FSharp.Compiler.AbstractIL.IL+ILTypeDefAdditionalFlags: ILTypeDefAdditionalFlags CanContainExtensionMethods +FSharp.Compiler.AbstractIL.IL+ILTypeDefAdditionalFlags: ILTypeDefAdditionalFlags IsKnownToBeAttribute +FSharp.Compiler.AbstractIL.IL+ILTypeDefAdditionalFlags: ILTypeDefAdditionalFlags None +FSharp.Compiler.AbstractIL.IL+ILTypeDefAdditionalFlags: Int32 value__ FSharp.Compiler.AbstractIL.IL+ILTypeDefKind+Tags: Int32 Class FSharp.Compiler.AbstractIL.IL+ILTypeDefKind+Tags: Int32 Delegate FSharp.Compiler.AbstractIL.IL+ILTypeDefKind+Tags: Int32 Enum @@ -1853,6 +1859,7 @@ FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILThisConvention FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILType FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILTypeDef FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess +FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILTypeDefAdditionalFlags FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILTypeDefKind FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILTypeDefLayout FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILTypeDefs @@ -1866,6 +1873,9 @@ FSharp.Compiler.AbstractIL.IL: ILAttributes emptyILCustomAttrs FSharp.Compiler.AbstractIL.IL: ILAttributes get_emptyILCustomAttrs() FSharp.Compiler.AbstractIL.IL: ILAttributes mkILCustomAttrs(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILAttribute]) FSharp.Compiler.AbstractIL.IL: ILAttributes mkILCustomAttrsFromArray(ILAttribute[]) +FSharp.Compiler.AbstractIL.IL: ILAttributesStored emptyILCustomAttrsStored +FSharp.Compiler.AbstractIL.IL: ILAttributesStored get_emptyILCustomAttrsStored() +FSharp.Compiler.AbstractIL.IL: ILAttributesStored mkILCustomAttrsComputed(Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,FSharp.Compiler.AbstractIL.IL+ILAttribute[]]) FSharp.Compiler.AbstractIL.IL: ILAttributesStored storeILCustomAttrs(ILAttributes) FSharp.Compiler.AbstractIL.IL: ILEventDefs emptyILEvents FSharp.Compiler.AbstractIL.IL: ILEventDefs get_emptyILEvents() diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl index 4dc729720c0..646e067251e 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl @@ -1483,6 +1483,7 @@ FSharp.Compiler.AbstractIL.IL+ILType: System.String QualifiedName FSharp.Compiler.AbstractIL.IL+ILType: System.String ToString() FSharp.Compiler.AbstractIL.IL+ILType: System.String get_BasicQualifiedName() FSharp.Compiler.AbstractIL.IL+ILType: System.String get_QualifiedName() +FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean CanContainExtensionMethods FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean HasSecurity FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean IsAbstract FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean IsClass @@ -1496,6 +1497,7 @@ FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean IsSerializable FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean IsSpecialName FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean IsStruct FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean IsStructOrEnum +FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean get_CanContainExtensionMethods() FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean get_HasSecurity() FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean get_IsAbstract() FSharp.Compiler.AbstractIL.IL+ILTypeDef: Boolean get_IsClass() @@ -1525,7 +1527,7 @@ FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILPropertyDefs Properties FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILPropertyDefs get_Properties() FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILSecurityDecls SecurityDecls FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILSecurityDecls get_SecurityDecls() -FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILTypeDef With(Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.Reflection.TypeAttributes], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILTypeDefLayout], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILType]], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILGenericParameterDef]], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILType]], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILMethodDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILTypeDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILFieldDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILMethodImplDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILEventDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILPropertyDefs], Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILAttributes], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILSecurityDecls]) +FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILTypeDef With(Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.Reflection.TypeAttributes], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILTypeDefLayout], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILType]], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILGenericParameterDef]], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILType]], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILMethodDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILTypeDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILFieldDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILMethodImplDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILEventDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILPropertyDefs], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILTypeDefAdditionalFlags], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILAttributesStored], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILSecurityDecls]) FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILTypeDefAccess Access FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILTypeDefAccess get_Access() FSharp.Compiler.AbstractIL.IL+ILTypeDef: ILTypeDefLayout Layout @@ -1543,7 +1545,7 @@ FSharp.Compiler.AbstractIL.IL+ILTypeDef: System.Reflection.TypeAttributes get_At FSharp.Compiler.AbstractIL.IL+ILTypeDef: System.String Name FSharp.Compiler.AbstractIL.IL+ILTypeDef: System.String ToString() FSharp.Compiler.AbstractIL.IL+ILTypeDef: System.String get_Name() -FSharp.Compiler.AbstractIL.IL+ILTypeDef: Void .ctor(System.String, System.Reflection.TypeAttributes, ILTypeDefLayout, Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILType], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILGenericParameterDef], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILType], ILMethodDefs, ILTypeDefs, ILFieldDefs, ILMethodImplDefs, ILEventDefs, ILPropertyDefs, Boolean, ILSecurityDecls, ILAttributes) +FSharp.Compiler.AbstractIL.IL+ILTypeDef: Void .ctor(System.String, System.Reflection.TypeAttributes, ILTypeDefLayout, Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILType], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILGenericParameterDef], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.AbstractIL.IL+ILType], ILMethodDefs, ILTypeDefs, ILFieldDefs, ILMethodImplDefs, ILEventDefs, ILPropertyDefs, ILTypeDefAdditionalFlags, ILSecurityDecls, ILAttributesStored) FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess+Nested: ILMemberAccess Item FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess+Nested: ILMemberAccess get_Item() FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess+Tags: Int32 Nested @@ -1574,6 +1576,10 @@ FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess: Int32 GetHashCode(System.Collecti FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess: Int32 Tag FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess: Int32 get_Tag() FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess: System.String ToString() +FSharp.Compiler.AbstractIL.IL+ILTypeDefAdditionalFlags: ILTypeDefAdditionalFlags CanContainExtensionMethods +FSharp.Compiler.AbstractIL.IL+ILTypeDefAdditionalFlags: ILTypeDefAdditionalFlags IsKnownToBeAttribute +FSharp.Compiler.AbstractIL.IL+ILTypeDefAdditionalFlags: ILTypeDefAdditionalFlags None +FSharp.Compiler.AbstractIL.IL+ILTypeDefAdditionalFlags: Int32 value__ FSharp.Compiler.AbstractIL.IL+ILTypeDefKind+Tags: Int32 Class FSharp.Compiler.AbstractIL.IL+ILTypeDefKind+Tags: Int32 Delegate FSharp.Compiler.AbstractIL.IL+ILTypeDefKind+Tags: Int32 Enum @@ -1853,6 +1859,7 @@ FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILThisConvention FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILType FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILTypeDef FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILTypeDefAccess +FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILTypeDefAdditionalFlags FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILTypeDefKind FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILTypeDefLayout FSharp.Compiler.AbstractIL.IL: FSharp.Compiler.AbstractIL.IL+ILTypeDefs @@ -1866,6 +1873,9 @@ FSharp.Compiler.AbstractIL.IL: ILAttributes emptyILCustomAttrs FSharp.Compiler.AbstractIL.IL: ILAttributes get_emptyILCustomAttrs() FSharp.Compiler.AbstractIL.IL: ILAttributes mkILCustomAttrs(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.AbstractIL.IL+ILAttribute]) FSharp.Compiler.AbstractIL.IL: ILAttributes mkILCustomAttrsFromArray(ILAttribute[]) +FSharp.Compiler.AbstractIL.IL: ILAttributesStored emptyILCustomAttrsStored +FSharp.Compiler.AbstractIL.IL: ILAttributesStored get_emptyILCustomAttrsStored() +FSharp.Compiler.AbstractIL.IL: ILAttributesStored mkILCustomAttrsComputed(Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,FSharp.Compiler.AbstractIL.IL+ILAttribute[]]) FSharp.Compiler.AbstractIL.IL: ILAttributesStored storeILCustomAttrs(ILAttributes) FSharp.Compiler.AbstractIL.IL: ILEventDefs emptyILEvents FSharp.Compiler.AbstractIL.IL: ILEventDefs get_emptyILEvents() diff --git a/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs b/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs index 560dbceeb66..8038cb8ad38 100644 --- a/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs @@ -115,10 +115,10 @@ type PreTypeDefData = mkILMethods [] let typeAttributes = TypeAttributes.Public - let customAttrs = mkILCustomAttrs [] + ILTypeDef(this.Name, typeAttributes, ILTypeDefLayout.Auto, [], [], - None, methodsDefs, mkILTypeDefs [], mkILFields [], emptyILMethodImpls, mkILEvents [], mkILProperties [], false, - emptyILSecurityDecls, customAttrs) + None, methodsDefs, mkILTypeDefs [], mkILFields [], emptyILMethodImpls, mkILEvents [], mkILProperties [], + ILTypeDefAdditionalFlags.None, emptyILSecurityDecls, emptyILCustomAttrsStored) type PreTypeDef(data: PreTypeDefData) = let typeDef = data.TypeDef From f6e24e85c2977b655630c9bd8f666c21d1e95c29 Mon Sep 17 00:00:00 2001 From: ijklam <43789618+Tangent-90@users.noreply.github.com> Date: Thu, 27 Jun 2024 21:16:13 +0800 Subject: [PATCH 3/4] Improve completion after method/property override (#17292) Co-authored-by: Vlad Zarytovskii --- .../.FSharp.Compiler.Service/8.0.400.md | 1 + src/Compiler/Checking/InfoReader.fs | 77 ++- src/Compiler/Checking/InfoReader.fsi | 31 ++ src/Compiler/Service/FSharpCheckerResults.fs | 474 +++++++++++++++--- .../Service/ServiceDeclarationLists.fs | 16 +- .../Service/ServiceDeclarationLists.fsi | 3 + src/Compiler/Service/ServiceParseTreeWalk.fs | 10 +- src/Compiler/Service/ServiceParsedInputOps.fs | 148 +++++- .../Service/ServiceParsedInputOps.fsi | 13 +- src/Compiler/pars.fsy | 19 +- ...vice.SurfaceArea.netstandard20.release.bsl | 37 +- .../Expression/Object - Class 06.fs.bsl | 23 +- .../Expression/Object - Class 15.fs.bsl | 21 + .../CompletionProviderTests.fs | 146 ++++-- 14 files changed, 874 insertions(+), 145 deletions(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md index 12a54d572fc..471fb842ab5 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md +++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md @@ -35,4 +35,5 @@ * Reduce allocations in compiler checking via `ValueOption` usage ([PR #16822](https://github.com/dotnet/fsharp/pull/16822)) * Use AsyncLocal instead of ThreadStatic to hold Cancellable.Token ([PR #17156](https://github.com/dotnet/fsharp/pull/17156)) * Showing and inserting correct name of entities from unopened namespace/module ([Issue #14375](https://github.com/dotnet/fsharp/issues/14375), [PR #17261](https://github.com/dotnet/fsharp/pull/17261)) +* Improve completion after method/property override ([PR #17292](https://github.com/dotnet/fsharp/pull/17292)) * Support lazy custom attributes calculation for `ILTypeDef` public API, improve `ExtensionAttribute` presence detecting perf. ([PR #16168](https://github.com/dotnet/fsharp/pull/16168)) diff --git a/src/Compiler/Checking/InfoReader.fs b/src/Compiler/Checking/InfoReader.fs index f81d89a900a..3a36122e89e 100644 --- a/src/Compiler/Checking/InfoReader.fs +++ b/src/Compiler/Checking/InfoReader.fs @@ -26,7 +26,7 @@ open FSharp.Compiler.TypeHierarchy open FSharp.Compiler.TypeRelations /// Use the given function to select some of the member values from the members of an F# type -let SelectImmediateMemberVals g optFilter f (tcref: TyconRef) = +let SelectImmediateMemberVals g optFilter f withExplicitImpl (tcref: TyconRef) = let chooser (vref: ValRef) = match vref.MemberInfo with // The 'when' condition is a workaround for the fact that values providing @@ -34,7 +34,7 @@ let SelectImmediateMemberVals g optFilter f (tcref: TyconRef) = // These cannot be selected directly via the "." notation. // However, it certainly is useful to be able to publish these values, as we can in theory // optimize code to make direct calls to these methods. - | Some membInfo when not (ValRefIsExplicitImpl g vref) -> + | Some membInfo when withExplicitImpl || not (ValRefIsExplicitImpl g vref) -> f membInfo vref | _ -> None @@ -53,7 +53,7 @@ let TrySelectMemberVal g optFilter ty pri _membInfo (vref: ValRef) = else None -let rec GetImmediateIntrinsicMethInfosOfTypeAux (optFilter, ad) g amap m origTy metadataTy = +let rec GetImmediateIntrinsicMethInfosOfTypeAux (optFilter, ad) g amap m withExplicitImpl origTy metadataTy = let minfos = match metadataOfTy g metadataTy with @@ -77,25 +77,28 @@ let rec GetImmediateIntrinsicMethInfosOfTypeAux (optFilter, ad) g amap m origTy // In this case convert to the .NET Tuple type that carries metadata and try again if isAnyTupleTy g metadataTy then let betterMetadataTy = convertToTypeWithMetadataIfPossible g metadataTy - GetImmediateIntrinsicMethInfosOfTypeAux (optFilter, ad) g amap m origTy betterMetadataTy + GetImmediateIntrinsicMethInfosOfTypeAux (optFilter, ad) g amap m withExplicitImpl origTy betterMetadataTy // Function types support methods FSharpFunc<_, _>.FromConverter and friends from .NET metadata, // but not instance methods (you can't write "f.Invoke(x)", you have to write "f x") elif isFunTy g metadataTy then let betterMetadataTy = convertToTypeWithMetadataIfPossible g metadataTy - GetImmediateIntrinsicMethInfosOfTypeAux (optFilter, ad) g amap m origTy betterMetadataTy + GetImmediateIntrinsicMethInfosOfTypeAux (optFilter, ad) g amap m withExplicitImpl origTy betterMetadataTy |> List.filter (fun minfo -> not minfo.IsInstance) else match tryTcrefOfAppTy g metadataTy with | ValueNone -> [] | ValueSome tcref -> - SelectImmediateMemberVals g optFilter (TrySelectMemberVal g optFilter origTy None) tcref + SelectImmediateMemberVals g optFilter (TrySelectMemberVal g optFilter origTy None) withExplicitImpl tcref let minfos = minfos |> List.filter (IsMethInfoAccessible amap m ad) minfos /// Query the immediate methods of an F# type, not taking into account inherited methods. The optFilter /// parameter is an optional name to restrict the set of properties returned. let GetImmediateIntrinsicMethInfosOfType (optFilter, ad) g amap m ty = - GetImmediateIntrinsicMethInfosOfTypeAux (optFilter, ad) g amap m ty ty + GetImmediateIntrinsicMethInfosOfTypeAux (optFilter, ad) g amap m false ty ty + +let GetImmediateIntrinsicMethInfosWithExplicitImplOfType (optFilter, ad) g amap m ty = + GetImmediateIntrinsicMethInfosOfTypeAux (optFilter, ad) g amap m true ty ty /// Query the immediate methods of an F# type, not taking into account inherited methods. The optFilter /// parameter is an optional name to restrict the set of properties returned. @@ -185,7 +188,7 @@ type PropertyCollector(g, amap, m, ty, optFilter, ad) = member _.Close() = [ for KeyValue(_, pinfo) in props -> pinfo ] -let rec GetImmediateIntrinsicPropInfosOfTypeAux (optFilter, ad) g amap m origTy metadataTy = +let rec GetImmediateIntrinsicPropInfosOfTypeAux (optFilter, ad) g amap m withExplicitImpl origTy metadataTy = let pinfos = match metadataOfTy g metadataTy with @@ -216,13 +219,13 @@ let rec GetImmediateIntrinsicPropInfosOfTypeAux (optFilter, ad) g amap m origTy // In this case convert to the .NET Tuple type that carries metadata and try again if isAnyTupleTy g metadataTy || isFunTy g metadataTy then let betterMetadataTy = convertToTypeWithMetadataIfPossible g metadataTy - GetImmediateIntrinsicPropInfosOfTypeAux (optFilter, ad) g amap m origTy betterMetadataTy + GetImmediateIntrinsicPropInfosOfTypeAux (optFilter, ad) g amap m withExplicitImpl origTy betterMetadataTy else match tryTcrefOfAppTy g metadataTy with | ValueNone -> [] | ValueSome tcref -> let propCollector = PropertyCollector(g, amap, m, origTy, optFilter, ad) - SelectImmediateMemberVals g None (fun membInfo vref -> propCollector.Collect(membInfo, vref); None) tcref |> ignore + SelectImmediateMemberVals g None (fun membInfo vref -> propCollector.Collect(membInfo, vref); None) withExplicitImpl tcref |> ignore propCollector.Close() let pinfos = pinfos |> List.filter (IsPropInfoAccessible g amap m ad) @@ -230,8 +233,11 @@ let rec GetImmediateIntrinsicPropInfosOfTypeAux (optFilter, ad) g amap m origTy /// Query the immediate properties of an F# type, not taking into account inherited properties. The optFilter /// parameter is an optional name to restrict the set of properties returned. -let rec GetImmediateIntrinsicPropInfosOfType (optFilter, ad) g amap m ty = - GetImmediateIntrinsicPropInfosOfTypeAux (optFilter, ad) g amap m ty ty +let GetImmediateIntrinsicPropInfosOfType (optFilter, ad) g amap m ty = + GetImmediateIntrinsicPropInfosOfTypeAux (optFilter, ad) g amap m false ty ty + +let GetImmediateIntrinsicPropInfosWithExplicitImplOfType (optFilter, ad) g amap m ty = + GetImmediateIntrinsicPropInfosOfTypeAux (optFilter, ad) g amap m true ty ty // Checks whether the given type has an indexer property. let IsIndexerType g amap ty = @@ -655,6 +661,44 @@ type InfoReader(g: TcGlobals, amap: Import.ImportMap) as this = PropInfosEquivByNameAndSig EraseNone g amap m, (fun pinfo -> pinfo.PropertyName)) + //type A() = + // abstract E: int with get, set + // default val E = 0 with get + // Will get (A::E with get, A::E with get, set) + // ----- + //type A() = + // member val A = 0 with get, set + //type B() = + // inherit A() + // static member val A = 0 + // Will get (static B::A, None) + static let FilterOverridesOfPropInfosWithOverridenProp findFlag g amap m props = + let checkProp prop prop2 = + not(obj.ReferenceEquals(prop, prop2)) && + PropInfosEquivByNameAndSig EraseNone g amap m prop prop2 && + if prop.HasGetter && prop.HasSetter then false + elif prop.HasGetter then prop2.HasSetter + elif prop.HasSetter then prop2.HasGetter + else false + + let rec findPropBefore prop hasMetTheProp = + function + | props :: t when hasMetTheProp -> + match props |> List.tryFind (checkProp prop) with + | Some p -> ValueSome p + | None -> findPropBefore prop true t + | props :: t -> + if props |> List.exists (fun i -> obj.ReferenceEquals(prop, i)) then + match props |> List.tryFind (checkProp prop) with + | Some p -> ValueSome p + | None -> findPropBefore prop true t + else findPropBefore prop false t + | _ -> ValueNone + + props + |> FilterOverridesOfPropInfos findFlag g amap m + |> List.map (List.map (fun prop -> struct(prop, if findFlag = FindMemberFlag.IgnoreOverrides || prop.IsNewSlot then ValueNone else findPropBefore prop false props))) + /// Exclude methods from super types which have the same signature as a method in a more specific type. static let ExcludeHiddenOfMethInfosImpl g amap m (minfos: MethInfo list list) = minfos @@ -905,6 +949,12 @@ type InfoReader(g: TcGlobals, amap: Import.ImportMap) as this = member infoReader.GetIntrinsicPropInfosOfType optFilter ad allowMultiIntfInst findFlag m ty = infoReader.GetIntrinsicPropInfoSetsOfType optFilter ad allowMultiIntfInst findFlag m ty |> List.concat + /// Get the flattened list of intrinsic properties in the hierarchy + member infoReader.GetIntrinsicPropInfoWithOverriddenPropOfType optFilter ad allowMultiIntfInst findFlag m ty = + infoReader.GetRawIntrinsicPropertySetsOfType(optFilter, ad, allowMultiIntfInst, m, ty) + |> FilterOverridesOfPropInfosWithOverridenProp findFlag infoReader.g infoReader.amap m + |> List.concat + member _.GetTraitInfosInType optFilter ty = GetImmediateTraitsInfosOfType optFilter g ty @@ -958,6 +1008,9 @@ let GetIntrinsicMethInfosOfType (infoReader: InfoReader) optFilter ad allowMulti let GetIntrinsicPropInfosOfType (infoReader: InfoReader) optFilter ad allowMultiIntfInst findFlag m ty = infoReader.GetIntrinsicPropInfosOfType optFilter ad allowMultiIntfInst findFlag m ty +let GetIntrinsicPropInfoWithOverriddenPropOfType (infoReader: InfoReader) optFilter ad allowMultiIntfInst findFlag m ty = + infoReader.GetIntrinsicPropInfoWithOverriddenPropOfType optFilter ad allowMultiIntfInst findFlag m ty + let TryFindIntrinsicNamedItemOfType (infoReader: InfoReader) (nm, ad, includeConstraints) findFlag m ty = infoReader.TryFindIntrinsicNamedItemOfType (nm, ad, includeConstraints) findFlag m ty diff --git a/src/Compiler/Checking/InfoReader.fsi b/src/Compiler/Checking/InfoReader.fsi index 3e8ceb927ca..6638c1c39e7 100644 --- a/src/Compiler/Checking/InfoReader.fsi +++ b/src/Compiler/Checking/InfoReader.fsi @@ -34,6 +34,16 @@ val GetImmediateIntrinsicMethInfosOfType: ty: TType -> MethInfo list +/// Query the immediate methods of an F# type, not taking into account inherited methods. The optFilter +/// parameter is an optional name to restrict the set of properties returned. +val GetImmediateIntrinsicMethInfosWithExplicitImplOfType: + optFilter: string option * ad: AccessorDomain -> + g: TcGlobals -> + amap: ImportMap -> + m: range -> + ty: TType -> + MethInfo list + /// A helper type to help collect properties. /// /// Join up getters and setters which are not associated in the F# data structure @@ -55,6 +65,16 @@ val GetImmediateIntrinsicPropInfosOfType: ty: TType -> PropInfo list +/// Query the immediate properties of an F# type, not taking into account inherited properties. The optFilter +/// parameter is an optional name to restrict the set of properties returned. +val GetImmediateIntrinsicPropInfosWithExplicitImplOfType: + optFilter: string option * ad: AccessorDomain -> + g: TcGlobals -> + amap: ImportMap -> + m: range -> + ty: TType -> + PropInfo list + /// Checks whether the given type has an indexer property. val IsIndexerType: g: TcGlobals -> amap: ImportMap -> ty: TType -> bool @@ -261,6 +281,17 @@ val GetIntrinsicPropInfosOfType: ty: TType -> PropInfo list +/// Get the flattened list of intrinsic properties in the hierarchy. If the PropInfo is get-only or set-only, try to find its setter or getter from the hierarchy. +val GetIntrinsicPropInfoWithOverriddenPropOfType: + infoReader: InfoReader -> + optFilter: string option -> + ad: AccessorDomain -> + allowMultiIntfInst: AllowMultiIntfInstantiations -> + findFlag: FindMemberFlag -> + m: range -> + ty: TType -> + struct (PropInfo * PropInfo voption) list + /// Perform type-directed name resolution of a particular named member in an F# type val TryFindIntrinsicNamedItemOfType: infoReader: InfoReader -> diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index 46ea4bce4f5..cab12da5cdf 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -579,6 +579,71 @@ type internal TypeCheckInfo Some(StripSelfRefCell(g, vref.BaseOrThisInfo, vref.TauType)) | _, _ -> None + /// Build a CompletionItem + let CompletionItemWithMoreSetting + (ty: TyconRef voption) + (assemblySymbol: AssemblySymbol voption) + minorPriority + insertText + displayText + (item: ItemWithInst) + = + let kind = + match item.Item with + | Item.DelegateCtor _ + | Item.CtorGroup _ -> CompletionItemKind.Method false + | Item.MethodGroup(_, minfos, _) -> + match minfos with + | [] -> CompletionItemKind.Method false + | minfo :: _ -> CompletionItemKind.Method minfo.IsExtensionMember + | Item.AnonRecdField _ + | Item.RecdField _ + | Item.UnionCaseField _ + | Item.Property _ -> CompletionItemKind.Property + | Item.Event _ -> CompletionItemKind.Event + | Item.ILField _ + | Item.Value _ -> CompletionItemKind.Field + | Item.CustomOperation _ -> CompletionItemKind.CustomOperation + // These items are not given a completion kind. This could be reviewed + | Item.ActivePatternResult _ + | Item.ExnCase _ + | Item.ImplicitOp _ + | Item.ModuleOrNamespaces _ + | Item.Trait _ + | Item.TypeVar _ + | Item.Types _ + | Item.UnionCase _ + | Item.UnqualifiedType _ + | Item.NewDef _ + | Item.SetterArg _ + | Item.CustomBuilder _ + | Item.OtherName _ + | Item.ActivePatternCase _ -> CompletionItemKind.Other + + let isUnresolved = + match assemblySymbol with + | ValueSome x -> Some x.UnresolvedSymbol + | _ -> None + + let ty = + match ty with + | ValueSome x -> Some x + | _ -> None + + { + ItemWithInst = item + MinorPriority = minorPriority + Kind = kind + IsOwnMember = false + Type = ty + Unresolved = isUnresolved + CustomInsertText = insertText + CustomDisplayText = displayText + } + + let CompletionItem (ty: TyconRef voption) (assemblySymbol: AssemblySymbol voption) (item: ItemWithInst) = + CompletionItemWithMoreSetting ty assemblySymbol 0 ValueNone ValueNone item + let CollectParameters (methods: MethInfo list) amap m : Item list = methods |> List.collect (fun meth -> @@ -898,59 +963,6 @@ type internal TypeCheckInfo if p >= 0 then Some p else None - /// Build a CompetionItem - let CompletionItem (ty: TyconRef voption) (assemblySymbol: AssemblySymbol voption) (item: ItemWithInst) = - let kind = - match item.Item with - | Item.DelegateCtor _ - | Item.CtorGroup _ -> CompletionItemKind.Method false - | Item.MethodGroup(_, minfos, _) -> - match minfos with - | [] -> CompletionItemKind.Method false - | minfo :: _ -> CompletionItemKind.Method minfo.IsExtensionMember - | Item.AnonRecdField _ - | Item.RecdField _ - | Item.UnionCaseField _ - | Item.Property _ -> CompletionItemKind.Property - | Item.Event _ -> CompletionItemKind.Event - | Item.ILField _ - | Item.Value _ -> CompletionItemKind.Field - | Item.CustomOperation _ -> CompletionItemKind.CustomOperation - // These items are not given a completion kind. This could be reviewed - | Item.ActivePatternResult _ - | Item.ExnCase _ - | Item.ImplicitOp _ - | Item.ModuleOrNamespaces _ - | Item.Trait _ - | Item.TypeVar _ - | Item.Types _ - | Item.UnionCase _ - | Item.UnqualifiedType _ - | Item.NewDef _ - | Item.SetterArg _ - | Item.CustomBuilder _ - | Item.OtherName _ - | Item.ActivePatternCase _ -> CompletionItemKind.Other - - let isUnresolved = - match assemblySymbol with - | ValueSome x -> Some x.UnresolvedSymbol - | _ -> None - - let ty = - match ty with - | ValueSome x -> Some x - | _ -> None - - { - ItemWithInst = item - MinorPriority = 0 - Kind = kind - IsOwnMember = false - Type = ty - Unresolved = isUnresolved - } - let DefaultCompletionItem item = CompletionItem ValueNone ValueNone item let CompletionItemSuggestedName displayName = @@ -961,6 +973,8 @@ type internal TypeCheckInfo Kind = CompletionItemKind.SuggestedName IsOwnMember = false Unresolved = None + CustomInsertText = ValueNone + CustomDisplayText = ValueNone } let getItem (x: ItemWithInst) = x.Item @@ -1045,45 +1059,340 @@ type internal TypeCheckInfo |> Option.defaultValue completions /// Gets all methods that a type can override, but has not yet done so. - let GetOverridableMethods pos typeNameRange = - let isMethodOverridable alreadyOverridden (candidate: MethInfo) = + let GetOverridableMethods pos ctx (typeNameRange: range) spacesBeforeOverrideKeyword hasThis isStatic = + let checkImplementedSlotDeclareType ty slots = + slots + |> Option.map (List.exists (fun (TSlotSig(declaringType = ty2)) -> typeEquiv g ty ty2)) + |> Option.defaultValue false + + let isMethodOverridable superTy alreadyOverridden (candidate: MethInfo) = not candidate.IsFinal && not ( alreadyOverridden - |> List.exists (MethInfosEquivByNameAndSig EraseNone true g amap range0 candidate) + |> ResizeArray.exists (fun i -> + MethInfosEquivByNameAndSig EraseNone true g amap range0 candidate i + && (tyconRefEq g candidate.DeclaringTyconRef i.DeclaringTyconRef + || checkImplementedSlotDeclareType superTy (Option.attempt (fun () -> i.ImplementedSlotSignatures)))) ) + let isMethodOptionOverridable superTy alreadyOverridden candidate = + candidate + |> ValueOption.map (fun i -> isMethodOverridable superTy alreadyOverridden i) + |> ValueOption.defaultValue false + + let isPropertyOverridable superTy alreadyOverridden (candidate: PropInfo) = + if candidate.IsVirtualProperty then + let getterOverridden, setterOverridden = + alreadyOverridden + |> List.filter (fun i -> + PropInfosEquivByNameAndSig EraseNone g amap range0 candidate i + && (tyconRefEq g candidate.DeclaringTyconRef i.DeclaringTyconRef + || checkImplementedSlotDeclareType superTy (Option.attempt (fun () -> i.ImplementedSlotSignatures)))) + |> List.fold + (fun (getterOverridden, setterOverridden) i -> getterOverridden || i.HasGetter, setterOverridden || i.HasSetter) + (false, false) + + not getterOverridden, not setterOverridden + else + false, false + + let rec checkOrGenerateArgName (nameSet: HashSet<_>) name = + let name = if String.IsNullOrEmpty name then "arg" else name + + if nameSet.Add(name) then + name + else + checkOrGenerateArgName nameSet $"{name}_{nameSet.Count}" + let (nenv, ad), m = GetBestEnvForPos pos + let denv = nenv.DisplayEnv - sResolutions.CapturedNameResolutions - |> ResizeArray.tryPick (fun r -> - match r.Item with - | Item.Types(_, ty :: _) when equals r.Range typeNameRange && isAppTy g ty -> - let superTy = - (tcrefOfAppTy g ty).TypeContents.tcaug_super |> Option.defaultValue g.obj_ty + let checkMethAbstractAndGetImplementBody (meth: MethInfo) implementBody = + if meth.IsAbstract then + if nenv.DisplayEnv.openTopPathsSorted.Force() |> List.contains [ "System" ] then + "raise (NotImplementedException())" + else + "raise (System.NotImplementedException())" + else + implementBody + + let newlineIndent = + Environment.NewLine + String.make (spacesBeforeOverrideKeyword + 4) ' ' + + let getOverridableMethods superTy (overriddenMethods: MethInfo list) overriddenProperties = + // Do not check a method with same name twice + //type AA() = + // abstract a: unit -> unit + // default _.a() = printfn "A" + //type BB() = + // inherit AA() + // member _.a() = printfn "B" (* This method covered the `AA.a` *) + //type CC() = + // inherit BB() + // override | (* Here should not suggest to override `AA.a` *) + let checkedMethods = ResizeArray(overriddenMethods) + + let isInterface = isInterfaceTy g superTy + + // reuse between props and methods + let argNames = HashSet() + + let overridableProps = + let generatePropertyOverrideBody (prop: PropInfo) getterMeth setterMeth = + argNames.Clear() + + let parameters = + prop.GetParamNamesAndTypes(amap, m) + |> List.map (fun (ParamNameAndType(name, ty)) -> + let name = + name + |> Option.map _.idText + |> Option.defaultValue String.Empty + |> checkOrGenerateArgName argNames + + $"{name}: {stringOfTy denv ty}") + |> String.concat ", " + + let retTy = prop.GetPropertyType(amap, m) + let retTy = stringOfTy denv retTy + + let getter, getterWithBody = + match getterMeth with + | ValueSome meth -> + let implementBody = + checkMethAbstractAndGetImplementBody + meth + ($"base.{prop.DisplayName}" + (if prop.IsIndexer then $"({parameters})" else "")) + + let getter = $"get ({parameters}): {retTy}" + getter, $"{getter} = {implementBody}" + | _ -> String.Empty, String.Empty + + let setter, setterWithBody = + match setterMeth with + | ValueSome meth -> + let argValue = checkOrGenerateArgName argNames "value" + + let implementBody = + checkMethAbstractAndGetImplementBody + meth + ($"base.{prop.DisplayName}" + + (if prop.IsIndexer then $"({parameters})" else String.Empty) + + $" <- {argValue}") + + let parameters = if prop.IsIndexer then $"({parameters}) " else String.Empty + let setter = $"set {parameters}({argValue}: {retTy})" + setter, $"{setter} = {implementBody}" + | _ -> String.Empty, String.Empty + + let keywordAnd = + if getterMeth.IsNone || setterMeth.IsNone then + String.Empty + else + " and " + + let this = if hasThis || prop.IsStatic then String.Empty else "this." + + let getterWithBody = + if String.IsNullOrWhiteSpace getterWithBody then + String.Empty + else + getterWithBody + newlineIndent + + let name = $"{prop.DisplayName} with {getter}{keywordAnd}{setter}" + + let textInCode = + this + + prop.DisplayName + + newlineIndent + + "with " + + getterWithBody + + keywordAnd + + setterWithBody + + name, textInCode + + GetIntrinsicPropInfoWithOverriddenPropOfType + infoReader + None + ad + TypeHierarchy.AllowMultiIntfInstantiations.No + FindMemberFlag.PreferOverrides + range0 + superTy + |> List.choose (fun struct (prop, baseProp) -> + let getterMeth = + if prop.HasGetter then + ValueSome prop.GetterMethod + else + baseProp |> ValueOption.map _.GetterMethod + + let setterMeth = + if prop.HasSetter then + ValueSome prop.SetterMethod + else + baseProp |> ValueOption.map _.SetterMethod + + let isGetterOverridable, isSetterOverridable = + isPropertyOverridable superTy overriddenProperties prop + + let isGetterOverridable = + isGetterOverridable + && isMethodOptionOverridable superTy checkedMethods getterMeth - let overriddenMethods = - GetImmediateIntrinsicMethInfosOfType (None, ad) g amap typeNameRange ty - |> List.filter (fun x -> x.IsDefiniteFSharpOverride) + let isSetterOverridable = + isSetterOverridable + && isMethodOptionOverridable superTy checkedMethods setterMeth - let overridableMethods = - GetIntrinsicMethInfosOfType - infoReader + let canPick = + prop.IsStatic = isStatic && (isGetterOverridable || isSetterOverridable) + + getterMeth |> ValueOption.iter checkedMethods.Add + setterMeth |> ValueOption.iter checkedMethods.Add + + if not canPick then None - ad - TypeHierarchy.AllowMultiIntfInstantiations.No - FindMemberFlag.PreferOverrides - range0 - superTy - |> List.filter (isMethodOverridable overriddenMethods) - |> List.groupBy (fun x -> x.DisplayName) - |> List.map (fun (name, overloads) -> - Item.MethodGroup(name, overloads, None) + else + let getterMeth = if isGetterOverridable then getterMeth else ValueNone + let setterMeth = if isSetterOverridable then setterMeth else ValueNone + let name, textInCode = generatePropertyOverrideBody prop getterMeth setterMeth + + Item.Property( + name, + [ + prop + if baseProp.IsSome then + baseProp.Value + ], + None + ) |> ItemWithNoInst - |> DefaultCompletionItem) + |> CompletionItemWithMoreSetting ValueNone ValueNone -1 (ValueSome textInCode) (ValueSome name) + |> Some) - Some(overridableMethods, nenv.DisplayEnv, m) - | _ -> None) + let overridableMeths = + let generateMethodOverrideBody (meth: MethInfo) = + argNames.Clear() + + let parameters = + meth.GetParamNames() + |> List.zip (meth.GetParamTypes(amap, m, meth.FormalMethodInst)) + |> List.map (fun (types, names) -> + let names = + names + |> List.zip types + |> List.map (fun (ty, name) -> + let name = + name |> Option.defaultValue String.Empty |> checkOrGenerateArgName argNames + + $"{name}: {stringOfTy denv ty}") + |> String.concat ", " + + $"({names})") + |> String.concat " " + + let retTy = meth.GetFSharpReturnType(amap, m, meth.FormalMethodInst) + + let name = $"{meth.DisplayName} {parameters}: {stringOfTy denv retTy}" + + let textInCode = + let nameWithThis = + if hasThis || not meth.IsInstance then + $"{name} = " + else + $"this.{name} = " + + let implementBody = + checkMethAbstractAndGetImplementBody meth $"base.{meth.DisplayName}{parameters}" + + nameWithThis + newlineIndent + implementBody + + name, textInCode + + GetIntrinsicMethInfosOfType + infoReader + None + ad + TypeHierarchy.AllowMultiIntfInstantiations.No + FindMemberFlag.PreferOverrides + range0 + superTy + |> List.choose (fun meth -> + let canPick = + meth.IsInstance <> isStatic + && isMethodOverridable superTy checkedMethods meth + && (not isInterface + || not (tyconRefEq g meth.DeclaringTyconRef g.system_Object_tcref)) + + checkedMethods.Add meth + + if not canPick then + None + else + let name, textInCode = generateMethodOverrideBody meth + + Item.MethodGroup(name, [ meth ], None) + |> ItemWithNoInst + |> CompletionItemWithMoreSetting ValueNone ValueNone -1 (ValueSome textInCode) (ValueSome name) + |> Some) + + overridableProps @ overridableMeths + + let getTyFromTypeNamePos (endPos: pos) = + let nameResItems = + GetPreciseItemsFromNameResolution(endPos.Line, endPos.Column, None, ResolveTypeNamesToTypeRefs, ResolveOverloads.Yes) + + match nameResItems with + | NameResResult.Members(ls, _, _) -> + ls + |> List.tryPick (function + | { Item = Item.Types(_, ty :: _) } -> Some ty + | _ -> None) + | _ -> None + + let ctx = + match ctx with + | MethodOverrideCompletionContext.Class -> + sResolutions.CapturedNameResolutions + |> ResizeArray.tryPick (fun r -> + match r.Item with + | Item.Types(_, ty :: _) when equals r.Range typeNameRange && isAppTy g ty -> + let superTy = + (tcrefOfAppTy g ty).TypeContents.tcaug_super |> Option.defaultValue g.obj_ty + + Some(ty, superTy) + | _ -> None) + + | MethodOverrideCompletionContext.Interface mTy -> + sResolutions.CapturedNameResolutions + |> ResizeArray.tryPick (fun r -> + match r.Item with + | Item.Types(_, ty :: _) when equals r.Range typeNameRange && isAppTy g ty -> + let superTy = getTyFromTypeNamePos mTy.End |> Option.defaultValue g.obj_ty + Some(ty, superTy) + | _ -> None) + | MethodOverrideCompletionContext.ObjExpr m -> + let _, quals = GetExprTypingForPosition(m.End) + + quals + |> Array.tryFind (fun (_, _, _, r) -> posEq m.Start r.Start) + |> Option.map (fun (ty, _, _, _) -> ty, getTyFromTypeNamePos typeNameRange.End |> Option.defaultValue g.obj_ty) + + match ctx with + | Some(ty, superTy) -> + let overriddenMethods = + GetImmediateIntrinsicMethInfosWithExplicitImplOfType (None, ad) g amap typeNameRange ty + |> List.filter (fun x -> x.IsDefiniteFSharpOverride) + + let overriddenProperties = + GetImmediateIntrinsicPropInfosWithExplicitImplOfType (None, ad) g amap typeNameRange ty + |> List.filter (fun x -> x.IsDefiniteFSharpOverride) + + let overridableMethods = + getOverridableMethods superTy overriddenMethods overriddenProperties + + Some(overridableMethods, denv, m) + | _ -> None /// Gets all field identifiers of a union case that can be referred to in a pattern. let GetUnionCaseFields caseIdRange alreadyReferencedFields = @@ -1579,6 +1888,8 @@ type internal TypeCheckInfo IsOwnMember = false Type = None Unresolved = None + CustomInsertText = ValueNone + CustomDisplayText = ValueNone }) match declaredItems with @@ -1641,7 +1952,8 @@ type internal TypeCheckInfo getDeclaredItemsNotInRangeOpWithAllSymbols () |> Option.bind (FilterRelevantItemsBy getItem2 None IsPatternCandidate) - | Some(CompletionContext.MethodOverride enclosingTypeNameRange) -> GetOverridableMethods pos enclosingTypeNameRange + | Some(CompletionContext.MethodOverride(ctx, enclosingTypeNameRange, spacesBeforeOverrideKeyword, hasThis, isStatic)) -> + GetOverridableMethods pos ctx enclosingTypeNameRange spacesBeforeOverrideKeyword hasThis isStatic // Other completions | cc -> diff --git a/src/Compiler/Service/ServiceDeclarationLists.fs b/src/Compiler/Service/ServiceDeclarationLists.fs index 14b506bff86..dd6727a5ba9 100644 --- a/src/Compiler/Service/ServiceDeclarationLists.fs +++ b/src/Compiler/Service/ServiceDeclarationLists.fs @@ -86,7 +86,9 @@ type CompletionItem = IsOwnMember: bool MinorPriority: int Type: TyconRef option - Unresolved: UnresolvedSymbol option } + Unresolved: UnresolvedSymbol option + CustomInsertText: string voption + CustomDisplayText: string voption } member x.Item = x.ItemWithInst.Item [] @@ -1125,19 +1127,23 @@ type DeclarationListInfo(declarations: DeclarationListItem[], isForType: bool, i match u.Namespace with | [||] -> u.DisplayName | ns -> (ns |> String.concat ".") + "." + u.DisplayName - | None -> x.Item.DisplayName) + | None when x.CustomDisplayText.IsSome -> x.CustomDisplayText.Value + | None -> x.Item.DisplayName + ) |> List.map ( let textInDeclList item = match item.Unresolved with | Some u -> u.DisplayName + | None when item.CustomDisplayText.IsSome -> item.CustomDisplayText.Value | None -> item.Item.DisplayNameCore let textInCode (item: CompletionItem) = match item.Item with | Item.TypeVar (name, typar) -> (if typar.StaticReq = Syntax.TyparStaticReq.None then "'" else " ^") + name | _ -> - match item.Unresolved with - | Some u -> u.DisplayName - | None -> item.Item.DisplayName + match item.Unresolved, item.CustomInsertText with + | Some u, _ -> u.DisplayName + | None, ValueSome textInCode -> textInCode + | None, _ -> item.Item.DisplayName if not supportsPreferExtsMethodsOverProperty then // we don't pay the cost of filtering specific to RFC-1137 // nor risk a change in behaviour for the intellisense item list diff --git a/src/Compiler/Service/ServiceDeclarationLists.fsi b/src/Compiler/Service/ServiceDeclarationLists.fsi index 4611ff86a16..3aeaa11112d 100644 --- a/src/Compiler/Service/ServiceDeclarationLists.fsi +++ b/src/Compiler/Service/ServiceDeclarationLists.fsi @@ -91,6 +91,9 @@ type internal CompletionItem = Type: TyconRef option Unresolved: UnresolvedSymbol option + + CustomInsertText: string voption + CustomDisplayText: string voption } member Item: Item diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fs b/src/Compiler/Service/ServiceParseTreeWalk.fs index c395c9422d2..42a461db90d 100644 --- a/src/Compiler/Service/ServiceParseTreeWalk.fs +++ b/src/Compiler/Service/ServiceParseTreeWalk.fs @@ -599,9 +599,16 @@ module SyntaxTraversal = | _ -> () for b in binds do yield dive b b.RangeOfBindingWithRhs (traverseSynBinding path) - for SynInterfaceImpl(bindings = binds) in ifaces do + for SynInterfaceImpl(ty, withKeyword, binds, members, range) in ifaces do + let path = + SyntaxNode.SynMemberDefn(SynMemberDefn.Interface(ty, withKeyword, Some members, range)) + :: path + for b in binds do yield dive b b.RangeOfBindingWithRhs (traverseSynBinding path) + + for m in members do + yield dive m m.Range (traverseSynMemberDefn path (fun _ -> None)) ] |> pick expr @@ -984,6 +991,7 @@ module SyntaxTraversal = x |> normalizeMembersToDealWithPeculiaritiesOfGettersAndSetters path (fun _ -> None) ] + |> pick x | ok -> ok | SynMemberDefn.Inherit(synType, _identOption, range) -> traverseInherit (synType, range) diff --git a/src/Compiler/Service/ServiceParsedInputOps.fs b/src/Compiler/Service/ServiceParsedInputOps.fs index 3f379c0f8b6..0f6f3bc0530 100644 --- a/src/Compiler/Service/ServiceParsedInputOps.fs +++ b/src/Compiler/Service/ServiceParsedInputOps.fs @@ -70,6 +70,12 @@ type PatternContext = /// Any other position in a pattern that does not need special handling | Other +[] +type MethodOverrideCompletionContext = + | Class + | Interface of mInterfaceName: range + | ObjExpr of mExpr: range + [] type CompletionContext = /// Completion context cannot be determined due to errors @@ -107,7 +113,12 @@ type CompletionContext = | Pattern of context: PatternContext /// Completing a method override (e.g. override this.ToStr|) - | MethodOverride of enclosingTypeNameRange: range + | MethodOverride of + ctx: MethodOverrideCompletionContext * + enclosingTypeNameRange: range * + spacesBeforeOverrideKeyword: int * + hasThis: bool * + isStatic: bool type ShortIdent = string @@ -1076,6 +1087,20 @@ module ParsedInput = | Operator "op_Equality" (SynExpr.Ident id, _) -> Some id | _ -> None + let posAfterRangeAndBetweenSpaces (lineStr: string) (m: range) pos = + let rec loop max i = + if i >= lineStr.Length || i >= max then true + elif Char.IsWhiteSpace lineStr[i] then loop max (i + 1) + else false + + posGt pos m.End && pos.Line = m.End.Line && loop pos.Column m.End.Column + + let rangeContainsPosOrIsSpacesBetweenRangeAndPos (lineStr: string) m pos = + rangeContainsPos m pos + // pos is before m + || posLt pos m.Start + || posAfterRangeAndBetweenSpaces lineStr m pos + let findSetters argList = match argList with | SynExpr.Paren(SynExpr.Tuple(false, parameters, _, _), _, _, _) -> @@ -1434,6 +1459,12 @@ module ParsedInput = |> List.tryPick (fun pat -> TryGetCompletionContextInPattern true pat None pos) |> Option.orElseWith (fun () -> defaultTraverse expr) + // { new | } + | SynExpr.ComputationExpr(expr = SynExpr.ArbitraryAfterError _) when + lineStr.Trim().Split(' ') |> Array.contains "new" + -> + Some(CompletionContext.Inherit(InheritanceContext.Unknown, ([], None))) + | _ -> defaultTraverse expr member _.VisitRecordField(path, copyOpt, field) = @@ -1491,36 +1522,123 @@ module ParsedInput = (SynBinding(headPat = headPat; trivia = trivia; returnInfo = returnInfo) as synBinding) ) = - let isOverride leadingKeyword = + let isOverrideOrMember leadingKeyword = + match leadingKeyword with + | SynLeadingKeyword.Override _ + | SynLeadingKeyword.Member _ -> true + | _ -> false + + let isStaticMember leadingKeyword = + match leadingKeyword with + | SynLeadingKeyword.StaticMember _ -> true + | _ -> false + + let isMember leadingKeyword = match leadingKeyword with - | SynLeadingKeyword.Override _ -> true + | SynLeadingKeyword.Member _ -> true | _ -> false - let overrideContext path = + let overrideContext path (mOverride: range) hasThis isStatic isMember = match path with - | _ :: SyntaxNode.SynTypeDefn(SynTypeDefn(typeInfo = SynComponentInfo(longId = [ enclosingType ]))) :: _ -> - Some(CompletionContext.MethodOverride enclosingType.idRange) + | _ :: SyntaxNode.SynTypeDefn(SynTypeDefn(typeInfo = SynComponentInfo(longId = [ enclosingType ]))) :: _ when + not isMember + -> + Some( + CompletionContext.MethodOverride( + MethodOverrideCompletionContext.Class, + enclosingType.idRange, + mOverride.StartColumn, + hasThis, + isStatic + ) + ) + | SyntaxNode.SynMemberDefn(SynMemberDefn.Interface(interfaceType = ty)) :: SyntaxNode.SynTypeDefn(SynTypeDefn( + typeInfo = SynComponentInfo(longId = [ enclosingType ]))) :: _ + | _ :: SyntaxNode.SynMemberDefn(SynMemberDefn.Interface(interfaceType = ty)) :: SyntaxNode.SynTypeDefn(SynTypeDefn( + typeInfo = SynComponentInfo(longId = [ enclosingType ]))) :: _ -> + let ty = + match ty with + | SynType.App(typeName = ty) -> ty + | _ -> ty + + Some( + CompletionContext.MethodOverride( + MethodOverrideCompletionContext.Interface ty.Range, + enclosingType.idRange, + mOverride.StartColumn, + hasThis, + isStatic + ) + ) + | SyntaxNode.SynMemberDefn(SynMemberDefn.Interface(interfaceType = ty)) :: (SyntaxNode.SynExpr(SynExpr.ObjExpr _) as expr) :: _ + | _ :: SyntaxNode.SynMemberDefn(SynMemberDefn.Interface(interfaceType = ty)) :: (SyntaxNode.SynExpr(SynExpr.ObjExpr _) as expr) :: _ -> + let ty = + match ty with + | SynType.App(typeName = ty) -> ty + | _ -> ty + + Some( + CompletionContext.MethodOverride( + MethodOverrideCompletionContext.ObjExpr expr.Range, + ty.Range, + mOverride.StartColumn, + hasThis, + isStatic + ) + ) + | SyntaxNode.SynExpr(SynExpr.ObjExpr(objType = ty)) as expr :: _ -> + let ty = + match ty with + | SynType.App(typeName = ty) -> ty + | _ -> ty + + Some( + CompletionContext.MethodOverride( + MethodOverrideCompletionContext.ObjExpr expr.Range, + ty.Range, + mOverride.StartColumn, + hasThis, + isStatic + ) + ) | _ -> Some CompletionContext.Invalid match returnInfo with - | Some(SynBindingReturnInfo(range = m)) when rangeContainsPos m pos -> Some CompletionContext.Type + | Some(SynBindingReturnInfo(range = m)) when rangeContainsPosOrIsSpacesBetweenRangeAndPos lineStr m pos -> + Some CompletionContext.Type | _ -> match headPat with + // static member | + | SynPat.FromParseError _ when isStaticMember trivia.LeadingKeyword -> + overrideContext path trivia.LeadingKeyword.Range false true false + + // override | + | SynPat.FromParseError _ when isOverrideOrMember trivia.LeadingKeyword && lineStr.[pos.Column - 1] = ' ' -> + overrideContext path trivia.LeadingKeyword.Range false false (isMember trivia.LeadingKeyword) + // override _.| - | SynPat.FromParseError _ when isOverride trivia.LeadingKeyword -> overrideContext path + | SynPat.FromParseError _ when isOverrideOrMember trivia.LeadingKeyword -> + overrideContext path trivia.LeadingKeyword.Range true false (isMember trivia.LeadingKeyword) // override this.| | SynPat.Named(ident = SynIdent(ident = selfId)) when - isOverride trivia.LeadingKeyword && selfId.idRange.End.IsAdjacentTo pos + isOverrideOrMember trivia.LeadingKeyword && selfId.idRange.End.IsAdjacentTo pos -> - overrideContext path + overrideContext path trivia.LeadingKeyword.Range true false (isMember trivia.LeadingKeyword) // override this.ToStr| | SynPat.LongIdent(longDotId = SynLongIdent(id = [ _; methodId ])) when - isOverride trivia.LeadingKeyword && rangeContainsPos methodId.idRange pos + isOverrideOrMember trivia.LeadingKeyword + && rangeContainsPos methodId.idRange pos + -> + overrideContext path trivia.LeadingKeyword.Range true false (isMember trivia.LeadingKeyword) + + // static member A| + | SynPat.LongIdent(longDotId = SynLongIdent(id = [ methodId ])) when + isStaticMember trivia.LeadingKeyword && rangeContainsPos methodId.idRange pos -> - overrideContext path + overrideContext path trivia.LeadingKeyword.Range false true false | SynPat.LongIdent(longDotId = lidwd; argPats = SynArgPats.Pats pats; range = m) when rangeContainsPos m pos -> if rangeContainsPos lidwd.Range pos then @@ -1708,6 +1826,12 @@ module ParsedInput = Some(CompletionContext.ParameterList(att.TypeName.Range.End, findSetters att.ArgExpr)) else None) + + override _.VisitInterfaceSynMemberDefnType(_, synType: SynType) = + match synType with + | SynType.FromParseError(range = m) when rangeContainsPosOrIsSpacesBetweenRangeAndPos lineStr m pos -> + Some(CompletionContext.Inherit(InheritanceContext.Interface, ([], None))) + | _ -> None } let ctxt = SyntaxTraversal.Traverse(pos, parsedInput, visitor) diff --git a/src/Compiler/Service/ServiceParsedInputOps.fsi b/src/Compiler/Service/ServiceParsedInputOps.fsi index 336e6213a7a..427ffda43aa 100644 --- a/src/Compiler/Service/ServiceParsedInputOps.fsi +++ b/src/Compiler/Service/ServiceParsedInputOps.fsi @@ -42,6 +42,12 @@ type public PatternContext = /// Any other position in a pattern that does not need special handling | Other +[] +type MethodOverrideCompletionContext = + | Class + | Interface of mInterfaceName: range + | ObjExpr of mExpr: range + [] type public CompletionContext = /// Completion context cannot be determined due to errors @@ -79,7 +85,12 @@ type public CompletionContext = | Pattern of context: PatternContext /// Completing a method override (e.g. override this.ToStr|) - | MethodOverride of enclosingTypeNameRange: range + | MethodOverride of + ctx: MethodOverrideCompletionContext * + enclosingTypeNameRange: range * + spacesBeforeOverrideKeyword: int * + hasThis: bool * + isStatic: bool type public ModuleKind = { IsAutoOpen: bool diff --git a/src/Compiler/pars.fsy b/src/Compiler/pars.fsy index 8cc9523b1fd..7bc495e9139 100644 --- a/src/Compiler/pars.fsy +++ b/src/Compiler/pars.fsy @@ -2384,8 +2384,23 @@ objectImplementationMember: { let rangeStart = rhs parseState 1 $3 $1 $2 rangeStart } - | opt_attributes staticMemberOrMemberOrOverride error - { [] } + | opt_attributes staticMemberOrMemberOrOverride recover + { let rangeStart = rhs parseState 1 + let memFlagsBuilder, leadingKeyword = $2 + let flags = Some(memFlagsBuilder SynMemberKind.Member) + let xmlDoc = grabXmlDocAtRangeStart (parseState, $1, rangeStart) + let trivia = { LeadingKeyword = leadingKeyword; InlineKeyword = None; EqualsRange = None } + let mMember = rhs parseState 2 + let mEnd = mMember.EndRange + let bindingPat = patFromParseError (SynPat.Wild(mEnd)) + let expr = arbExpr ("objectImplementationMember1", mEnd) + let mWhole = rhs2 parseState 1 2 + let binding = + mkSynBinding + (xmlDoc, bindingPat) + (None, false, false, mWhole, DebugPointAtBinding.NoneAtInvisible, None, expr, mEnd, [], $1, flags, trivia) + + [SynMemberDefn.Member(binding, mWhole)] } | opt_attributes error memberCore opt_ODECLEND { [] } diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl index 646e067251e..e8647e3f6ba 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl @@ -2849,8 +2849,16 @@ FSharp.Compiler.EditorServices.CompletionContext+Inherit: FSharp.Compiler.Editor FSharp.Compiler.EditorServices.CompletionContext+Inherit: FSharp.Compiler.EditorServices.InheritanceContext get_context() FSharp.Compiler.EditorServices.CompletionContext+Inherit: System.Tuple`2[Microsoft.FSharp.Collections.FSharpList`1[System.String],Microsoft.FSharp.Core.FSharpOption`1[System.String]] get_path() FSharp.Compiler.EditorServices.CompletionContext+Inherit: System.Tuple`2[Microsoft.FSharp.Collections.FSharpList`1[System.String],Microsoft.FSharp.Core.FSharpOption`1[System.String]] path +FSharp.Compiler.EditorServices.CompletionContext+MethodOverride: Boolean get_hasThis() +FSharp.Compiler.EditorServices.CompletionContext+MethodOverride: Boolean get_isStatic() +FSharp.Compiler.EditorServices.CompletionContext+MethodOverride: Boolean hasThis +FSharp.Compiler.EditorServices.CompletionContext+MethodOverride: Boolean isStatic +FSharp.Compiler.EditorServices.CompletionContext+MethodOverride: FSharp.Compiler.EditorServices.MethodOverrideCompletionContext ctx +FSharp.Compiler.EditorServices.CompletionContext+MethodOverride: FSharp.Compiler.EditorServices.MethodOverrideCompletionContext get_ctx() FSharp.Compiler.EditorServices.CompletionContext+MethodOverride: FSharp.Compiler.Text.Range enclosingTypeNameRange FSharp.Compiler.EditorServices.CompletionContext+MethodOverride: FSharp.Compiler.Text.Range get_enclosingTypeNameRange() +FSharp.Compiler.EditorServices.CompletionContext+MethodOverride: Int32 get_spacesBeforeOverrideKeyword() +FSharp.Compiler.EditorServices.CompletionContext+MethodOverride: Int32 spacesBeforeOverrideKeyword FSharp.Compiler.EditorServices.CompletionContext+OpenDeclaration: Boolean get_isOpenType() FSharp.Compiler.EditorServices.CompletionContext+OpenDeclaration: Boolean isOpenType FSharp.Compiler.EditorServices.CompletionContext+ParameterList: FSharp.Compiler.Text.Position Item1 @@ -2904,7 +2912,7 @@ FSharp.Compiler.EditorServices.CompletionContext: Boolean get_IsUnionCaseFieldsD FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext AttributeApplication FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext Invalid FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext NewInherit(FSharp.Compiler.EditorServices.InheritanceContext, System.Tuple`2[Microsoft.FSharp.Collections.FSharpList`1[System.String],Microsoft.FSharp.Core.FSharpOption`1[System.String]]) -FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext NewMethodOverride(FSharp.Compiler.Text.Range) +FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext NewMethodOverride(FSharp.Compiler.EditorServices.MethodOverrideCompletionContext, FSharp.Compiler.Text.Range, Int32, Boolean, Boolean) FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext NewOpenDeclaration(Boolean) FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext NewParameterList(FSharp.Compiler.Text.Position, System.Collections.Generic.HashSet`1[System.String]) FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext NewPattern(FSharp.Compiler.EditorServices.PatternContext) @@ -3539,6 +3547,33 @@ FSharp.Compiler.EditorServices.MethodGroupItemParameter: System.String Canonical FSharp.Compiler.EditorServices.MethodGroupItemParameter: System.String ParameterName FSharp.Compiler.EditorServices.MethodGroupItemParameter: System.String get_CanonicalTypeTextForSorting() FSharp.Compiler.EditorServices.MethodGroupItemParameter: System.String get_ParameterName() +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext+Tags: Int32 Class +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext+Tags: Int32 Interface +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext+Tags: Int32 ObjExpr +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: Boolean Equals(FSharp.Compiler.EditorServices.MethodOverrideCompletionContext) +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: Boolean Equals(FSharp.Compiler.EditorServices.MethodOverrideCompletionContext, System.Collections.IEqualityComparer) +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: Boolean Equals(System.Object) +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: Boolean Equals(System.Object, System.Collections.IEqualityComparer) +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: Boolean IsClass +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: Boolean IsInterface +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: Boolean IsObjExpr +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: Boolean get_IsClass() +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: Boolean get_IsInterface() +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: Boolean get_IsObjExpr() +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: FSharp.Compiler.EditorServices.MethodOverrideCompletionContext Class +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: FSharp.Compiler.EditorServices.MethodOverrideCompletionContext NewInterface(FSharp.Compiler.Text.Range) +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: FSharp.Compiler.EditorServices.MethodOverrideCompletionContext NewObjExpr(FSharp.Compiler.Text.Range) +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: FSharp.Compiler.EditorServices.MethodOverrideCompletionContext get_Class() +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: FSharp.Compiler.EditorServices.MethodOverrideCompletionContext+Tags +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: FSharp.Compiler.Text.Range get_mExpr() +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: FSharp.Compiler.Text.Range get_mInterfaceName() +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: FSharp.Compiler.Text.Range mExpr +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: FSharp.Compiler.Text.Range mInterfaceName +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: Int32 GetHashCode() +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: Int32 GetHashCode(System.Collections.IEqualityComparer) +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: Int32 Tag +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: Int32 get_Tag() +FSharp.Compiler.EditorServices.MethodOverrideCompletionContext: System.String ToString() FSharp.Compiler.EditorServices.ModuleKind: Boolean Equals(FSharp.Compiler.EditorServices.ModuleKind) FSharp.Compiler.EditorServices.ModuleKind: Boolean Equals(FSharp.Compiler.EditorServices.ModuleKind, System.Collections.IEqualityComparer) FSharp.Compiler.EditorServices.ModuleKind: Boolean Equals(System.Object) diff --git a/tests/service/data/SyntaxTree/Expression/Object - Class 06.fs.bsl b/tests/service/data/SyntaxTree/Expression/Object - Class 06.fs.bsl index d5e42b7e065..1880c3c044f 100644 --- a/tests/service/data/SyntaxTree/Expression/Object - Class 06.fs.bsl +++ b/tests/service/data/SyntaxTree/Expression/Object - Class 06.fs.bsl @@ -8,7 +8,28 @@ ImplFile (ObjExpr (LongIdent (SynLongIdent ([T], [], [None])), Some (Const (Unit, (3,7--3,9)), None), Some (3,10--3,14), [], - [], [], (3,2--3,9), (3,0--4,13)), (3,0--4,13)); + [Member + (SynBinding + (None, Normal, false, false, [], + PreXmlDoc ((4,5), FSharp.Compiler.Xml.XmlDocCollector), + SynValData + (Some { IsInstance = true + IsDispatchSlot = false + IsOverrideOrExplicitImpl = true + IsFinal = false + GetterOrSetterIsCompilerGenerated = false + MemberKind = Member }, + SynValInfo + ([[SynArgInfo ([], false, None)]; []], + SynArgInfo ([], false, None)), None), + FromParseError (Wild (4,11--4,11), (4,11--4,11)), None, + ArbitraryAfterError + ("objectImplementationMember1", (4,11--4,11)), + (4,5--4,11), NoneAtInvisible, + { LeadingKeyword = Member (4,5--4,11) + InlineKeyword = None + EqualsRange = None }), (4,5--4,11))], [], (3,2--3,9), + (3,0--4,13)), (3,0--4,13)); Expr (Const (Unit, (6,0--6,2)), (6,0--6,2))], PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None, (1,0--6,2), { LeadingKeyword = Module (1,0--1,6) })], (true, true), diff --git a/tests/service/data/SyntaxTree/Expression/Object - Class 15.fs.bsl b/tests/service/data/SyntaxTree/Expression/Object - Class 15.fs.bsl index c9b041181af..ec08bebfe92 100644 --- a/tests/service/data/SyntaxTree/Expression/Object - Class 15.fs.bsl +++ b/tests/service/data/SyntaxTree/Expression/Object - Class 15.fs.bsl @@ -9,6 +9,27 @@ ImplFile (LongIdent (SynLongIdent ([T], [], [None])), Some (Const (Unit, (3,7--3,9)), None), Some (3,10--3,14), [], [Member + (SynBinding + (None, Normal, false, false, [], + PreXmlDoc ((4,5), FSharp.Compiler.Xml.XmlDocCollector), + SynValData + (Some { IsInstance = true + IsDispatchSlot = false + IsOverrideOrExplicitImpl = true + IsFinal = false + GetterOrSetterIsCompilerGenerated = false + MemberKind = Member }, + SynValInfo + ([[SynArgInfo ([], false, None)]; []], + SynArgInfo ([], false, None)), None), + FromParseError (Wild (4,11--4,11), (4,11--4,11)), None, + ArbitraryAfterError + ("objectImplementationMember1", (4,11--4,11)), + (4,5--4,11), NoneAtInvisible, + { LeadingKeyword = Member (4,5--4,11) + InlineKeyword = None + EqualsRange = None }), (4,5--4,11)); + Member (SynBinding (None, Normal, false, false, [], PreXmlDoc ((5,5), FSharp.Compiler.Xml.XmlDocCollector), diff --git a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs index 551b804bc37..09c9d6cefaf 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs @@ -1853,35 +1853,49 @@ type B () = inherit System.Dynamic.DynamicMetaObjectBinder () override x. + +let _ = + { new System.Dynamic.SetIndexBinder (null) with + member x. + } + +let _ = + { new System.Dynamic.DynamicMetaObjectBinder () with + member this. + } """ // SetIndexBinder inherits from DynamicMetaObjectBinder, but overrides and seals Bind and the ReturnType property - VerifyCompletionListExactly( - fileContents, - "override _.a", - [ - "BindDelegate" - "Equals" - "FallbackSetIndex" - "Finalize" - "GetHashCode" - "ToString" - ] - ) - - VerifyCompletionListExactly( - fileContents, - "override x.", - [ - "Bind" - "BindDelegate" - "Equals" - "Finalize" - "GetHashCode" - "get_ReturnType" - "ToString" - ] - ) + [ "override _.a"; "member x." ] + |> List.iter (fun i -> + VerifyCompletionListExactly( + fileContents, + i, + [ + "BindDelegate (site: System.Runtime.CompilerServices.CallSite<'T>, args: obj array): 'T" + "Equals (obj: obj): bool" + "FallbackSetIndex (target: System.Dynamic.DynamicMetaObject, indexes: System.Dynamic.DynamicMetaObject array, value: System.Dynamic.DynamicMetaObject, errorSuggestion: System.Dynamic.DynamicMetaObject): System.Dynamic.DynamicMetaObject" + "Finalize (): unit" + "GetHashCode (): int" + "ToString (): string" + ] + )) + + [ "override x."; "member this." ] + |> List.iter (fun i -> + VerifyCompletionListExactly( + fileContents, + i, + [ + "ReturnType with get (): System.Type" + "Bind (target: System.Dynamic.DynamicMetaObject, args: System.Dynamic.DynamicMetaObject array): System.Dynamic.DynamicMetaObject" + "BindDelegate (site: System.Runtime.CompilerServices.CallSite<'T>, args: obj array): 'T" + "Equals (obj: obj): bool" + "Finalize (): unit" + "GetHashCode (): int" + "ToString (): string" + ] + )) [] let ``Completion list for override does not contain virtual method if it is already overridden in the same type`` () = @@ -1914,9 +1928,83 @@ type C () = override A1 s = () """ - VerifyCompletionListExactly(fileContents, "override _.", [ "Equals"; "Finalize"; "GetHashCode" ]) - VerifyCompletionListExactly(fileContents, "override x.b", [ "A1"; "A2"; "Equals"; "Finalize"; "GetHashCode"; "ToString" ]) - VerifyCompletionListExactly(fileContents, "override x.c", [ "A2"; "Equals"; "Finalize"; "GetHashCode"; "ToString" ]) + VerifyCompletionListExactly(fileContents, "override _.", [ "Equals (obj: obj): bool"; "Finalize (): unit"; "GetHashCode (): int" ]) + + VerifyCompletionListExactly( + fileContents, + "override x.b", + [ + "A1 (arg: string): unit" + "A2 (): unit" + "Equals (obj: obj): bool" + "Finalize (): unit" + "GetHashCode (): int" + "ToString (): string" + ] + ) + + VerifyCompletionListExactly( + fileContents, + "override x.c", + [ + "A2 (): unit" + "Equals (obj: obj): bool" + "Finalize (): unit" + "GetHashCode (): int" + "ToString (): string" + ] + ) + + [] + let ``Completion list for override in interface implements does not contain method which is already overridden in the same type`` () = + let fileContents = + """ +type IA = + static abstract member A3: unit -> unit + static abstract member A4: unit -> unit + static abstract member P1: value: int -> int with get, set + static abstract member P2: int with get, set + +type IB = + abstract member A1: unit -> unit + abstract member A1: string -> unit + abstract member A2: unit -> unit + abstract member P1: int * bool -> int with get, set + abstract member P2: int with get, set + +type TA() = + interface IA with + static member + static member A3 (): unit = () + static member P2 + with get (): int = raise (System.NotImplementedException()) + interface IB with + member this.A1 (arg1: string): unit = () + member this.P2 + with get (): int = raise (System.NotImplementedException()) + member thisTA. +""" + + VerifyCompletionListExactly( + fileContents, + "static member ", + [ + "P1 with get (value: int): int and set (value: int) (value_1: int)" + "P2 with set (value: int)" + "A4 (): unit" + ] + ) + + VerifyCompletionListExactly( + fileContents, + "member thisTA.", + [ + "P1 with get (arg: int, arg_1: bool): int and set (arg: int, arg_1: bool) (value: int)" + "P2 with set (value: int)" + "A1 (): unit" + "A2 (): unit" + ] + ) [] let ``Completion list for override is empty when the caret is on the self identifier`` () = From a6f539d50f8ecd97441282d3fe6181daa23b1b01 Mon Sep 17 00:00:00 2001 From: Tuomas Hietanen Date: Thu, 27 Jun 2024 15:41:01 +0100 Subject: [PATCH 4/4] TypeProviders: Add inner base-exception description (#17251) * TypeProviders: Add inner base-exception description * Release notes docs updated * Update src/Compiler/TypedTree/tainted.fs Thanks @abelbraaksma Co-authored-by: Abel Braaksma * Update docs/release-notes/.FSharp.Compiler.Service/8.0.400.md Co-authored-by: Abel Braaksma * Added @abelbraaksma's suggestion --------- Co-authored-by: Abel Braaksma Co-authored-by: Vlad Zarytovskii Co-authored-by: Petr --- docs/release-notes/.FSharp.Compiler.Service/8.0.400.md | 1 + src/Compiler/TypedTree/tainted.fs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md index 471fb842ab5..5bda852e8f4 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md +++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md @@ -25,6 +25,7 @@ * Allow #nowarn to support the FS prefix on error codes to disable warnings ([Issue #17206](https://github.com/dotnet/fsharp/issues/16447), [PR #17209](https://github.com/dotnet/fsharp/pull/17209)) * Allow ParsedHashDirectives to have argument types other than strings ([Issue #17240](https://github.com/dotnet/fsharp/issues/16447), [PR #17209](https://github.com/dotnet/fsharp/pull/17209)) * Parser: better recovery for unfinished patterns ([PR #17231](https://github.com/dotnet/fsharp/pull/17231)) +* Expose inner exception information of TypeProviders to help diagnostics in IDE ([PR #17251](https://github.com/dotnet/fsharp/pull/17251)) * Parser: recover on empty match clause ([PR #17233](https://github.com/dotnet/fsharp/pull/17233)) ### Changed diff --git a/src/Compiler/TypedTree/tainted.fs b/src/Compiler/TypedTree/tainted.fs index f586f99a3cd..609e66fe0d4 100644 --- a/src/Compiler/TypedTree/tainted.fs +++ b/src/Compiler/TypedTree/tainted.fs @@ -101,11 +101,12 @@ type internal Tainted<'T> (context: TaintedContext, value: 'T) = | :? TypeProviderError -> reraise() | :? AggregateException as ae -> let errNum,_ = FSComp.SR.etProviderError("", "") - let messages = [for e in ae.InnerExceptions -> e.Message] + let messages = [for e in ae.InnerExceptions -> if isNull e.InnerException then e.Message else (e.Message + ": " + e.GetBaseException().Message)] raise <| TypeProviderError(errNum, this.TypeProviderDesignation, range, messages) | e -> let errNum,_ = FSComp.SR.etProviderError("", "") - raise <| TypeProviderError((errNum, e.Message), this.TypeProviderDesignation, range) + let error = if isNull e.InnerException then e.Message else (e.Message + ": " + e.GetBaseException().Message) + raise <| TypeProviderError((errNum, error), this.TypeProviderDesignation, range) member _.TypeProvider = Tainted<_>(context, context.TypeProvider)