Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Core/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Fix `Array.exists2` documentation examples to use equal-length arrays; the previous examples would throw `ArgumentException` at runtime instead of returning the documented `false`/`true` values. ([PR #19672](https://github.com/dotnet/fsharp/pull/19672))
* Move `Async.StartChild` to the "Starting Async Computations" docs category alongside `Async.StartChildAsTask`. ([Issue #19667](https://github.com/dotnet/fsharp/issues/19667))
* Add `InlineIfLambda` to `Array.init` ([PR #19869](https://github.com/dotnet/fsharp/pull/19869))
* Fix array and string slices with extreme reversed bounds to return correctly shaped empty results. ([Issue #20530](https://github.com/dotnet/fsharp/issues/20530), [PR #20557](https://github.com/dotnet/fsharp/pull/20557))
* Fix printf handling of -0.0 (negative zero) values for float, float32, and decimal values ([Issue #15557](https://github.com/dotnet/fsharp/issues/15557) and [Issue #15558](https://github.com/dotnet/fsharp/issues/15558), [PR #18147](https://github.com/dotnet/fsharp/pull/18147))


Expand Down
82 changes: 40 additions & 42 deletions src/FSharp.Core/prim-types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6274,9 +6274,27 @@ namespace Microsoft.FSharp.Core

low, high

let inline ComputeSliceRange bound start finish length =
let low =
match start with
| Some n when n >= bound -> n
| _ -> bound
let count =
if length = 0 then
0
else
let upper = bound + (length - 1)
let high =
match finish with
| Some n when n < upper -> n
| _ -> upper
if high < low then 0 else high - low + 1

low, count

let inline GetArraySlice (source: _ array) start finish =
let start, finish = ComputeSlice 0 start finish source.Length
GetArraySub source start (finish - start + 1)
let start, len = ComputeSliceRange 0 start finish source.Length
GetArraySub source start len

let inline SetArraySlice (target: _ array) start finish (source: _ array) =
let start = (match start with None -> 0 | Some n -> n)
Expand All @@ -6286,16 +6304,13 @@ namespace Microsoft.FSharp.Core
let inline GetArraySlice2D (source: _[,]) start1 finish1 start2 finish2 =
let bound1 = source.GetLowerBound(0)
let bound2 = source.GetLowerBound(1)
let start1, finish1 = ComputeSlice bound1 start1 finish1 (GetArray2DLength1 source)
let start2, finish2 = ComputeSlice bound2 start2 finish2 (GetArray2DLength2 source)
let len1 = (finish1 - start1 + 1)
let len2 = (finish2 - start2 + 1)
let start1, len1 = ComputeSliceRange bound1 start1 finish1 (GetArray2DLength1 source)
let start2, len2 = ComputeSliceRange bound2 start2 finish2 (GetArray2DLength2 source)
GetArray2DSub source start1 start2 len1 len2

let inline GetArraySlice2DFixed (source: _[,]) start finish index nonFixedDim =
let bound = source.GetLowerBound(nonFixedDim)
let start, finish = ComputeSlice bound start finish (GetArray2DLength source nonFixedDim)
let len = (finish - start + 1)
let start, len = ComputeSliceRange bound start finish (GetArray2DLength source nonFixedDim)
let dst = zeroCreate (if len < 0 then 0 else len)
let getArrayElem =
match nonFixedDim with
Expand Down Expand Up @@ -6339,21 +6354,16 @@ namespace Microsoft.FSharp.Core
let bound1 = source.GetLowerBound(0)
let bound2 = source.GetLowerBound(1)
let bound3 = source.GetLowerBound(2)
let start1, finish1 = ComputeSlice bound1 start1 finish1 (GetArray3DLength1 source)
let start2, finish2 = ComputeSlice bound2 start2 finish2 (GetArray3DLength2 source)
let start3, finish3 = ComputeSlice bound3 start3 finish3 (GetArray3DLength3 source)
let len1 = (finish1 - start1 + 1)
let len2 = (finish2 - start2 + 1)
let len3 = (finish3 - start3 + 1)
let start1, len1 = ComputeSliceRange bound1 start1 finish1 (GetArray3DLength1 source)
let start2, len2 = ComputeSliceRange bound2 start2 finish2 (GetArray3DLength2 source)
let start3, len3 = ComputeSliceRange bound3 start3 finish3 (GetArray3DLength3 source)
GetArray3DSub source start1 start2 start3 len1 len2 len3

let inline GetArraySlice3DFixedSingle (source: _[,,]) start1 finish1 start2 finish2 index nonFixedDim1 nonFixedDim2 =
let bound1 = source.GetLowerBound(nonFixedDim1)
let bound2 = source.GetLowerBound(nonFixedDim2)
let start1, finish1 = ComputeSlice bound1 start1 finish1 (GetArray3DLength source nonFixedDim1)
let start2, finish2 = ComputeSlice bound2 start2 finish2 (GetArray3DLength source nonFixedDim2)
let len1 = (finish1 - start1 + 1)
let len2 = (finish2 - start2 + 1)
let start1, len1 = ComputeSliceRange bound1 start1 finish1 (GetArray3DLength source nonFixedDim1)
let start2, len2 = ComputeSliceRange bound2 start2 finish2 (GetArray3DLength source nonFixedDim2)

let dst = Array2DZeroCreate (max 0 len1) (max 0 len2)
let getArrayElem =
Expand All @@ -6377,8 +6387,7 @@ namespace Microsoft.FSharp.Core

let inline GetArraySlice3DFixedDouble (source: _[,,]) start finish index1 index2 nonFixedDim =
let bound = source.GetLowerBound(nonFixedDim)
let start, finish = ComputeSlice bound start finish (GetArray3DLength source nonFixedDim)
let len = (finish - start + 1)
let start, len = ComputeSliceRange bound start finish (GetArray3DLength source nonFixedDim)
let dst = zeroCreate (if len < 0 then 0 else len)
let getArrayElem =
match nonFixedDim with
Expand Down Expand Up @@ -6465,26 +6474,19 @@ namespace Microsoft.FSharp.Core
let bound2 = source.GetLowerBound(1)
let bound3 = source.GetLowerBound(2)
let bound4 = source.GetLowerBound(3)
let start1, finish1 = ComputeSlice bound1 start1 finish1 (GetArray4DLength1 source)
let start2, finish2 = ComputeSlice bound2 start2 finish2 (GetArray4DLength2 source)
let start3, finish3 = ComputeSlice bound3 start3 finish3 (GetArray4DLength3 source)
let start4, finish4 = ComputeSlice bound4 start4 finish4 (GetArray4DLength4 source)
let len1 = (finish1 - start1 + 1)
let len2 = (finish2 - start2 + 1)
let len3 = (finish3 - start3 + 1)
let len4 = (finish4 - start4 + 1)
let start1, len1 = ComputeSliceRange bound1 start1 finish1 (GetArray4DLength1 source)
let start2, len2 = ComputeSliceRange bound2 start2 finish2 (GetArray4DLength2 source)
let start3, len3 = ComputeSliceRange bound3 start3 finish3 (GetArray4DLength3 source)
let start4, len4 = ComputeSliceRange bound4 start4 finish4 (GetArray4DLength4 source)
GetArray4DSub source start1 start2 start3 start4 len1 len2 len3 len4

let inline GetArraySlice4DFixedSingle (source: _[,,,]) start1 finish1 start2 finish2 start3 finish3 index nonFixedDim1 nonFixedDim2 nonFixedDim3 =
let bound1 = source.GetLowerBound(nonFixedDim1)
let bound2 = source.GetLowerBound(nonFixedDim2)
let bound3 = source.GetLowerBound(nonFixedDim3)
let start1, finish1 = ComputeSlice bound1 start1 finish1 (GetArray4DLength source nonFixedDim1)
let start2, finish2 = ComputeSlice bound2 start2 finish2 (GetArray4DLength source nonFixedDim2)
let start3, finish3 = ComputeSlice bound3 start3 finish3 (GetArray4DLength source nonFixedDim3)
let len1 = (finish1 - start1 + 1)
let len2 = (finish2 - start2 + 1)
let len3 = (finish3 - start3 + 1)
let _, len1 = ComputeSliceRange bound1 start1 finish1 (GetArray4DLength source nonFixedDim1)
let _, len2 = ComputeSliceRange bound2 start2 finish2 (GetArray4DLength source nonFixedDim2)
let _, len3 = ComputeSliceRange bound3 start3 finish3 (GetArray4DLength source nonFixedDim3)

let dst = Array3DZeroCreate (max len1 0) (max len2 0) (max len3 0)
let getArrayElem =
Expand Down Expand Up @@ -6516,10 +6518,8 @@ namespace Microsoft.FSharp.Core
let inline GetArraySlice4DFixedDouble (source: _[,,,]) start1 finish1 start2 finish2 index1 index2 nonFixedDim1 nonFixedDim2 =
let bound1 = source.GetLowerBound(nonFixedDim1)
let bound2 = source.GetLowerBound(nonFixedDim2)
let start1, finish1 = ComputeSlice bound1 start1 finish1 (GetArray4DLength source nonFixedDim1)
let start2, finish2 = ComputeSlice bound2 start2 finish2 (GetArray4DLength source nonFixedDim2)
let len1 = (finish1 - start1 + 1)
let len2 = (finish2 - start2 + 1)
let _, len1 = ComputeSliceRange bound1 start1 finish1 (GetArray4DLength source nonFixedDim1)
let _, len2 = ComputeSliceRange bound2 start2 finish2 (GetArray4DLength source nonFixedDim2)

let dst = Array2DZeroCreate (max len1 0) (max len2 0)
let getArrayElem =
Expand Down Expand Up @@ -6557,8 +6557,7 @@ namespace Microsoft.FSharp.Core

let inline GetArraySlice4DFixedTriple (source: _[,,,]) start1 finish1 index1 index2 index3 nonFixedDim1 =
let bound1 = source.GetLowerBound(nonFixedDim1)
let start1, finish1 = ComputeSlice bound1 start1 finish1 (GetArray4DLength source nonFixedDim1)
let len1 = (finish1 - start1 + 1)
let _, len1 = ComputeSliceRange bound1 start1 finish1 (GetArray4DLength source nonFixedDim1)
let dst = zeroCreate (max len1 0)
let getArrayElem =
match nonFixedDim1 with
Expand Down Expand Up @@ -6702,8 +6701,7 @@ namespace Microsoft.FSharp.Core
SetArraySlice4DFixedTriple target source index1 index2 index3 start4 finish4 3

let inline GetStringSlice (source: string) start finish =
let start, finish = ComputeSlice 0 start finish source.Length
let len = finish-start+1
let start, len = ComputeSliceRange 0 start finish source.Length
if len <= 0 then String.Empty
else source.Substring(start, len)

Expand Down
117 changes: 117 additions & 0 deletions tests/FSharp.Core.UnitTests/FSharp.Core/OperatorsModule1.fs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,123 @@ type OperatorsModule1() =
// null
CheckThrowsNullRefException(fun () -> Operators.OperatorIntrinsics.GetStringSlice null param1 param2 |> ignore)

static member GetterSlicingOverflowCases() =
let hi, lo = Int32.MaxValue, Int32.MinValue
let a1 = [|1;2;3|]
let a2 = Array2D.zeroCreate<int> 2 3
let a3 = Array3D.zeroCreate<int> 2 3 4
let a4 = Array4D.zeroCreate<int> 2 3 4 5
let positive = Array2D.zeroCreateBased<int> 3 5 2 3
let negative = Array2D.zeroCreateBased<int> -3 5 2 3
let empty = Array.CreateInstance(typeof<int>, [|0;2|], [|lo;0|]) :?> int[,]
let body name (args: obj[]) () =
let m = typeof<int option>.Assembly.GetType("Microsoft.FSharp.Core.Operators+OperatorIntrinsics").GetMethod(name)
Assert.NotNull m
m.MakeGenericMethod(typeof<int>).Invoke(null, args) :?> Array
let cases: (string * int list * (unit -> Array)) list =
[
"syntax 1D count two", [0], (fun () -> a1[hi..lo])
"syntax 1D count three", [0], (fun () -> a1[hi..(lo + 1)])
"syntax 2D axis 0", [0;3], (fun () -> a2[hi..lo, *])
"syntax 2D axis 1", [2;0], (fun () -> a2[*, hi..lo])
"syntax 3D axis 0", [0;3;4], (fun () -> a3[hi..lo, *, *])
"syntax 3D axis 1", [2;0;4], (fun () -> a3[*, hi..lo, *])
"syntax 3D axis 2", [2;3;0], (fun () -> a3[*, *, hi..lo])
"syntax 4D axis 0", [0;3;4;5], (fun () -> a4[hi..lo, *, *, *])
"syntax 4D axis 1", [2;0;4;5], (fun () -> a4[*, hi..lo, *, *])
"syntax 4D axis 2", [2;3;0;5], (fun () -> a4[*, *, hi..lo, *])
"syntax 4D axis 3", [2;3;4;0], (fun () -> a4[*, *, *, hi..lo])
"syntax 2D fixed", [0], (fun () -> a2[0, hi..lo])
"syntax 3D fixed single", [0;4], (fun () -> a3[0, hi..lo, *])
"syntax 3D fixed double", [0], (fun () -> a3[0, 0, hi..lo])
"syntax 4D fixed single", [0;4;5], (fun () -> a4[0, hi..lo, *, *])
"syntax 4D fixed double", [0;5], (fun () -> a4[0, 0, hi..lo, *])
"syntax 4D fixed triple", [0], (fun () -> a4[0, 0, 0, hi..lo])
"syntax 2D fixed loop", [0], (fun () -> a2[0, 1..lo])
"syntax 3D fixed loop", [0;4], (fun () -> a3[0, 1..lo, *])
"syntax 4D fixed loop", [0;4;5], (fun () -> a4[0, 1..lo, *, *])
"positive based", [0;3], (fun () -> positive[hi..lo, *])
"negative based", [0;3], (fun () -> negative[hi..lo, *])
#if NETCOREAPP
"inclusive endpoint", [0;2], (fun () ->
let endpoint = Array.CreateInstance(typeof<int>, [|1;2|], [|hi;0|]) :?> int[,]
endpoint[..lo, *])
#endif
"empty based explicit finish", [0;2], (fun () -> empty[hi..lo, *])
"empty based omitted finish", [0;2], (fun () -> empty[hi.., *])
"body 1D count two", [0], body "GetArraySlice" [|a1; Some hi; Some lo|]
"body 1D count three", [0], body "GetArraySlice" [|a1; Some hi; Some(lo + 1)|]
"body 2D", [0;3], body "GetArraySlice2D" [|a2; Some hi; Some lo; None; None|]
"body 3D", [0;3;4], body "GetArraySlice3D" [|a3; Some hi; Some lo; None; None; None; None|]
"body 4D", [0;3;4;5], body "GetArraySlice4D" [|a4; Some hi; Some lo; None; None; None; None; None; None|]
"body 2D fixed", [0], body "GetArraySlice2DFixed1" [|a2; 0; Some hi; Some lo|]
"body 3D fixed single", [0;4], body "GetArraySlice3DFixedSingle1" [|a3; 0; Some hi; Some lo; None; None|]
"body 3D fixed double", [0], body "GetArraySlice3DFixedDouble1" [|a3; 0; 0; Some hi; Some lo|]
"body 4D fixed single", [0;4;5], body "GetArraySlice4DFixedSingle1" [|a4; 0; Some hi; Some lo; None; None; None; None|]
"body 4D fixed double", [0;5], body "GetArraySlice4DFixedDouble1" [|a4; 0; 0; Some hi; Some lo; None; None|]
"body 4D fixed triple", [0], body "GetArraySlice4DFixedTriple4" [|a4; 0; 0; 0; Some hi; Some lo|]
"body 2D fixed loop", [0], body "GetArraySlice2DFixed1" [|a2; 0; Some 1; Some lo|]
"body 3D fixed loop", [0;4], body "GetArraySlice3DFixedSingle1" [|a3; 0; Some 1; Some lo; None; None|]
"body 4D fixed loop", [0;4;5], body "GetArraySlice4DFixedSingle1" [|a4; 0; Some 1; Some lo; None; None; None; None|]
]
cases |> Seq.map (fun (name, shape, slice) -> [|box name; box shape; box slice|])

static member private CheckSliceShape(expected: int list, actual: Array) =
Assert.AreEqual(expected.Length, actual.Rank)
expected |> List.iteri (fun d length ->
Assert.AreEqual(length, actual.GetLength(d))
Assert.AreEqual(0, actual.GetLowerBound(d)))

[<Theory; MemberData(nameof OperatorsModule1.GetterSlicingOverflowCases)>]
member _.GetterSlicingOverflowShape(_name: string, expected: int list, slice: unit -> Array) =
OperatorsModule1.CheckSliceShape(expected, slice())

[<Theory>]
[<InlineData(3, false)>]
[<InlineData(Int32.MaxValue, false)>]
[<InlineData(3, true)>]
[<InlineData(Int32.MaxValue, true)>]
member _.GetterSlicingOverflowString(start: int, callableBody: bool) =
let actual =
if callableBody then
let m = typeof<int option>.Assembly.GetType("Microsoft.FSharp.Core.Operators+OperatorIntrinsics").GetMethod("GetStringSlice")
Assert.NotNull m
m.Invoke(null, [|"hello"; Some start; Some Int32.MinValue|]) :?> string
else
"hello"[start..Int32.MinValue]
Assert.AreEqual(String.Empty, actual)

#if NETCOREAPP
[<Fact>]
member _.GetterSlicingOverflowFinishBeforeUpperEndpoint() =
let start = Int32.MaxValue - 1
let source = Array.CreateInstance(typeof<int>, [|2;2|], [|start;0|]) :?> int[,]
source[start, 0] <- 42
source[start, 1] <- 43
let actual = source[start..start, *]
OperatorsModule1.CheckSliceShape([1;2], actual)
Assert.AreEqual(42, actual[0, 0])
Assert.AreEqual(43, actual[0, 1])
#endif

[<Fact>]
member _.GetterSlicingControls() =
let hi, lo = Int32.MaxValue, Int32.MinValue
Assert.AreEqual([], [1..5][3..lo])
OperatorsModule1.CheckSliceShape([0], [|1;2;3|][3..(lo + 10)])
let source = Array2D.initBased -3 5 2 3 (fun i j -> 100 * i + j)
let actual = source[-3..-2, *]
OperatorsModule1.CheckSliceShape([2;3], actual)
for i in 0..1 do
for j in 0..2 do
Assert.AreEqual(source[i - 3, j + 5], actual[i, j])
let a2 = Array2D.zeroCreate<int> 2 3
OperatorsModule1.CheckSliceShape([0], a2[2, 1..0])
CheckThrowsIndexOutRangException(fun () -> a2[2, 0..0] |> ignore)
CheckThrowsNullRefException(fun () -> Operators.OperatorIntrinsics.GetArraySlice (null: int[]) (Some hi) (Some lo) |> ignore)
CheckThrowsNullRefException(fun () -> Operators.OperatorIntrinsics.GetArraySlice2DFixed1 (null: int[,]) 0 (Some hi) (Some lo) |> ignore)
CheckThrowsNullRefException(fun () -> Operators.OperatorIntrinsics.GetStringSlice null (Some hi) (Some lo) |> ignore)

[<Fact>]
member _.OptimizedRangesSetArraySlice() =
let param1 = Some(1)
Expand Down
Loading