From be0a281995ba95f4754e5df88d50d3661069e660 Mon Sep 17 00:00:00 2001 From: Leo Conforti Date: Mon, 26 Oct 2026 12:23:18 -0500 Subject: [PATCH 1/6] feat(js/ts): support System.Array.BinarySearch and ResizeArray.BinarySearch Map the System.Array and ResizeArray BinarySearch overloads to new Array.binarySearch, binarySearchRange and binarySearchRangeCount library functions. The search follows .NET's probe order, the range overloads raise ArgumentOutOfRangeException and ArgumentException like .NET, and a missing or null comparer becomes the default comparer that ResizeArray.Sort() uses. --- src/Fable.Transforms/Replacements.fs | 24 +++++++++ src/fable-library-ts/Array.fs | 44 ++++++++++++++++ tests/Js/Main/ArrayTests.fs | 78 ++++++++++++++++++++++++++++ tests/Js/Main/ResizeArrayTests.fs | 24 +++++++++ 4 files changed, 170 insertions(+) diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 8109a1ae2a..e519e3f749 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -2155,6 +2155,28 @@ let copyToArray (com: ICompiler) r t (i: CallInfo) args = Helper.LibCall(com, "Util", "copyToArray", t, args, i.SignatureArgTypes, genArgs = i.GenericArgs, ?loc = r) |> Some +let binarySearch (com: ICompiler) ctx r t rangeMeth (ar: Expr) args = + let call meth args comparer = + let comparer = + match comparer with + // .NET uses the default comparer when the comparer is null. The null literal + // gets here as Util.defaultOf() unless its type is nullable (see defaultof) + | None + | Some NullConst + | Some(MaybeCasted(Call(Import({ Selector = "defaultOf" }, _, _), _, _, _))) -> + makeComparer com ctx (getElementType ar.Type) + | Some comparer -> comparer + + Helper.LibCall(com, "Array", meth, t, ar :: args @ [ comparer ], ?loc = r) + |> Some + + match args with + | [ value ] -> call "binarySearch" [ value ] None + | [ value; comparer ] -> call "binarySearch" [ value ] (Some comparer) + | [ index; length; value ] -> call rangeMeth [ index; length; value ] None + | [ index; length; value; comparer ] -> call rangeMeth [ index; length; value ] (Some comparer) + | _ -> None + let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg, args with | ".ctor", _, [] -> makeResizeArray (getElementType t) [] |> Some @@ -2233,6 +2255,7 @@ let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (this | "IndexOf", Some ar, args -> let args = injectIndexOfArgs com ctx r i.GenericArgs (ar :: args) Helper.LibCall(com, "Array", "indexOf", t, args, ?loc = r) |> Some + | "BinarySearch", Some ar, args -> binarySearch com ctx r t "binarySearchRangeCount" ar args | "Insert", Some ar, [ idx; arg ] -> Helper.InstanceCall(ar, "splice", t, [ idx; makeIntConst 0; arg ], ?loc = r) |> Some @@ -2329,6 +2352,7 @@ let arrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: E Helper.LibCall(com, "Array", "indexOf", t, args, i.SignatureArgTypes, genArgs = i.GenericArgs, ?loc = r) |> Some + | "BinarySearch", None, ar :: args -> binarySearch com ctx r t "binarySearchRange" ar args | "GetEnumerator", Some arg, _ -> getEnumerator com r t arg |> Some | "Resize", None, args -> let args = diff --git a/src/fable-library-ts/Array.fs b/src/fable-library-ts/Array.fs index 9632b11662..8a95276aa3 100644 --- a/src/fable-library-ts/Array.fs +++ b/src/fable-library-ts/Array.fs @@ -242,6 +242,50 @@ let indexOf<'T> let contains<'T> (value: 'T) (array: 'T[]) ([] eq: IEqualityComparer<'T>) = indexOf array value None None eq >= 0 +// Follows .NET's probe order, so duplicates and comparers with side effects give the same results +let private binarySearchFrom (array: 'T[]) (index: int) (length: int) (value: 'T) (comparer: IComparer<'T>) = + let rec loop lo hi = + if lo > hi then + ~~~lo + else + let i = lo + ((hi - lo) >>> 1) + let c = comparer.Compare(array.[i], value) + + if c = 0 then + i + elif c < 0 then + loop (i + 1) hi + else + loop lo (i - 1) + + loop index (index + length - 1) + +let private checkBinarySearchRange (lengthName: string) (array: 'T[]) (index: int) (length: int) = + if index < 0 then + raise (System.ArgumentOutOfRangeException("index", "Non-negative number required.")) + + if length < 0 then + raise (System.ArgumentOutOfRangeException(lengthName, "Non-negative number required.")) + + if array.Length - index < length then + raise ( + System.ArgumentException( + "Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection." + ) + ) + +let binarySearch (array: 'T[]) (value: 'T) (comparer: IComparer<'T>) : int = + binarySearchFrom array 0 array.Length value comparer + +let binarySearchRange (array: 'T[]) (index: int) (length: int) (value: 'T) (comparer: IComparer<'T>) : int = + checkBinarySearchRange "length" array index length + binarySearchFrom array index length value comparer + +// ResizeArray's range overload, which names its length "count" +let binarySearchRangeCount (array: 'T[]) (index: int) (count: int) (value: 'T) (comparer: IComparer<'T>) : int = + checkBinarySearchRange "count" array index count + binarySearchFrom array index count value comparer + let empty cons = allocateArrayFromCons cons 0 let singleton value ([] cons: Cons<'T>) = diff --git a/tests/Js/Main/ArrayTests.fs b/tests/Js/Main/ArrayTests.fs index b0b90a14a6..6b1537ebe4 100644 --- a/tests/Js/Main/ArrayTests.fs +++ b/tests/Js/Main/ArrayTests.fs @@ -563,6 +563,84 @@ let tests = System.Array.IndexOf(myArray, Dog 3, 0, 1) |> equal -1 System.Array.IndexOf(myArray, Duck 5, 1) |> equal -1 + testCase "System.Array.BinarySearch works" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 0) |> equal -1 + System.Array.BinarySearch(xs, 1) |> equal 0 + System.Array.BinarySearch(xs, 2) |> equal -2 + System.Array.BinarySearch(xs, 7) |> equal 3 + System.Array.BinarySearch(xs, 8) |> equal -5 + System.Array.BinarySearch(xs, 10) |> equal -6 + System.Array.BinarySearch(([||]: int[]), 3) |> equal -1 + + testCase "System.Array.BinarySearch works with duplicates" <| fun () -> + System.Array.BinarySearch([|2; 2; 2; 2; 2; 2; 2; 2|], 2) |> equal 3 + System.Array.BinarySearch([|1; 2; 2; 2; 2; 3; 4; 5; 6|], 2) |> equal 4 + + testCase "System.Array.BinarySearch works with a range" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2) |> equal -2 + System.Array.BinarySearch(xs, 1, 3, 9) |> equal -5 + System.Array.BinarySearch(xs, 1, 2, 9) |> equal -4 + System.Array.BinarySearch(xs, 2, 0, 5) |> equal -3 + System.Array.BinarySearch(xs, 5, 0, 5) |> equal -6 + + testCase "System.Array.BinarySearch works with a comparer" <| fun () -> + let desc = ComparisonIdentity.FromFunction(fun (a: int) b -> compare b a) + let ys = [|9; 7; 5; 3; 1|] + System.Array.BinarySearch(ys, 3, desc) |> equal 3 + System.Array.BinarySearch(ys, 4, desc) |> equal -4 + System.Array.BinarySearch(ys, 1, 3, 3, desc) |> equal 3 + System.Array.BinarySearch([|1; 3; 5; 7; 9|], 7, (null: IComparer)) |> equal 3 + + testCase "System.Array.BinarySearch passes the array element to the comparer first" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + let logging = + { new IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + System.Array.BinarySearch(xs, 9, logging) |> equal 4 + List.ofSeq calls |> equal [(5, 9); (7, 9); (9, 9)] + calls.Clear() + System.Array.BinarySearch(xs, 4, logging) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (1, 4); (3, 4)] + + testCase "System.Array.BinarySearch works with other element types" <| fun () -> + let strings = [|"apple"; "banana"; "cherry"|] + System.Array.BinarySearch(strings, "banana") |> equal 1 + System.Array.BinarySearch(strings, "blue") |> equal -3 + let floats = [|1.5; 2.5; 3.5|] + System.Array.BinarySearch(floats, 2.5) |> equal 1 + System.Array.BinarySearch(floats, 3.0) |> equal -3 + let longs = [|1L; 5L; 10L|] + System.Array.BinarySearch(longs, 10L) |> equal 2 + System.Array.BinarySearch(longs, 6L) |> equal -3 + let things = [|{ MainThing = 1; OtherThing = "x" }; { MainThing = 2; OtherThing = "a" }; { MainThing = 2; OtherThing = "b" }|] + System.Array.BinarySearch(things, { MainThing = 2; OtherThing = "b" }) |> equal 2 + System.Array.BinarySearch(things, { MainThing = 1; OtherThing = "z" }) |> equal -2 + let animals = [|Duck 1; Duck 4; Dog 0|] + System.Array.BinarySearch(animals, Dog 0) |> equal 2 + System.Array.BinarySearch(animals, Duck 2) |> equal -2 + + testCase "System.Array.BinarySearch with a range checks its arguments" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> System.Array.BinarySearch(xs, -1, 2, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, -1, -1, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, 1, -1, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, 3, 3, 5)) |> equal "ArgumentException" + raised (fun () -> System.Array.BinarySearch(xs, 6, 0, 5)) |> equal "ArgumentException" + testCase "Array.findIndex works" <| fun () -> let xs = [|1.f; 2.f; 3.f; 4.f|] xs |> Array.findIndex ((=) 2.f) diff --git a/tests/Js/Main/ResizeArrayTests.fs b/tests/Js/Main/ResizeArrayTests.fs index fe0b8c22d6..b570150dfc 100644 --- a/tests/Js/Main/ResizeArrayTests.fs +++ b/tests/Js/Main/ResizeArrayTests.fs @@ -283,6 +283,30 @@ let tests = ns.Sort(comparer) Seq.toList ns |> equal [1;2;3] + testCase "ResizeArray.BinarySearch works" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + ra.BinarySearch(7) |> equal 3 + ra.BinarySearch(8) |> equal -5 + let desc = ComparisonIdentity.FromFunction(fun (a: int) b -> compare b a) + (ResizeArray [9; 7; 5; 3; 1]).BinarySearch(3, desc) |> equal 3 + ra.BinarySearch(1, 3, 7, Comparer.Default) |> equal 3 + ra.BinarySearch(1, 2, 8, Comparer.Default) |> equal -4 + ra.BinarySearch(1, 2, 3, null) |> equal 1 + + testCase "ResizeArray.BinarySearch with a range checks its arguments" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> ra.BinarySearch(-1, 2, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(1, -1, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(3, 3, 3, null)) |> equal "ArgumentException" + testCase "ResizeArray.ToArray works" <| fun () -> let li = ResizeArray<_>() li.Add(3.); li.Add(6.); li.Add(5.); li.Add(4.); li.Add(8.) From 6eb0563111da10e08b3e5e91333e0785af89a0a1 Mon Sep 17 00:00:00 2001 From: Leo Conforti Date: Mon, 26 Oct 2026 12:22:36 -0500 Subject: [PATCH 2/6] feat(python): support System.Array.BinarySearch and ResizeArray.BinarySearch Both use .NET's search loop, so arrays with duplicates and comparers that record their calls give .NET's answers. The range overloads check their arguments like .NET, raising ArgumentOutOfRangeException for a negative index or length (count for ResizeArray) and ArgumentException for a range past the end. The search lives in resize_array.py and only uses len() and indexing, so it serves Python lists and the Rust-backed arrays alike. An omitted or null comparer is the default comparer Fable passes to Array.sortInPlace. --- src/Fable.Transforms/Python/Replacements.fs | 22 +++++ .../fable_library/resize_array.py | 54 +++++++++++- tests/Python/TestArray.fs | 87 +++++++++++++++++++ tests/Python/TestResizeArray.fs | 26 ++++++ 4 files changed, 187 insertions(+), 2 deletions(-) diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index 248e3266ef..4fff6e121e 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -1850,6 +1850,26 @@ let injectIndexOfArgs com ctx r genArgs args = injectArg com ctx r "Array" "indexOf" genArgs args +/// resize_array's binary search indexes arrays and ResizeArrays alike. +/// As in .NET, an omitted or null comparer means the default one. +let binarySearch (com: ICompiler) ctx r t rangeMeth (ar: Expr) args = + let call meth args comparer = + let comparer = + match comparer with + | None + | Some NullConst -> makeComparer com ctx (getElementType ar.Type) + | Some comparer -> comparer + + Helper.LibCall(com, "resize_array", meth, t, ar :: args @ [ comparer ], ?loc = r) + |> Some + + match args with + | [ value ] -> call "binary_search" [ value ] None + | [ value; comparer ] -> call "binary_search" [ value ] (Some comparer) + | [ index; length; value ] -> call rangeMeth [ index; length; value ] None + | [ index; length; value; comparer ] -> call rangeMeth [ index; length; value ] (Some comparer) + | _ -> None + let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg, args with | ".ctor", _, [] -> makeResizeArray (getElementType t) [] |> Some @@ -1928,6 +1948,7 @@ let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (this Helper.LibCall(com, "resize_array", "index_of", t, args @ [ ar ], ?loc = r) |> Some + | "BinarySearch", Some ar, args -> binarySearch com ctx r t "binary_search_range_count" ar args | "Insert", Some ar, [ idx; arg ] -> Helper.InstanceCall(ar, "insert", t, [ idx; arg ], ?loc = r) |> Some | "InsertRange", Some ar, [ idx; arg ] -> Helper.LibCall(com, "resize_array", "insert_range_in_place", t, [ idx; arg; ar ], ?loc = r) @@ -2034,6 +2055,7 @@ let arrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: E Helper.LibCall(com, "array", "index_of", t, args, i.SignatureArgTypes, ?loc = r) |> Some + | "BinarySearch", None, ar :: args -> binarySearch com ctx r t "binary_search_range" ar args | "GetEnumerator", Some arg, _ -> getEnumerator com r t arg |> Some | "Resize", None, args -> let args = diff --git a/src/fable-library-py/fable_library/resize_array.py b/src/fable-library-py/fable_library/resize_array.py index e31c625c5f..d2b405f733 100644 --- a/src/fable-library-py/fable_library/resize_array.py +++ b/src/fable-library-py/fable_library/resize_array.py @@ -1,11 +1,12 @@ from __future__ import annotations -from collections.abc import Callable, Iterable +from collections.abc import Callable, Iterable, Sequence from typing import Any, cast from .core import int32 from .option import Option, some -from .protocols import IEnumerable_1 +from .protocols import IComparer_1, IEnumerable_1 +from .system import ArgumentException__ctor_Z721C83C5, ArgumentOutOfRangeException__ctor_Z384F8060 from .util import to_iterable @@ -100,6 +101,52 @@ def index_of[T](value: T, start: int, count: int | None, xs: list[T]) -> int: return -1 +def _check_range(size: int, index: int, count: int, count_name: str) -> None: + """Raise like .NET when index and count do not describe a range of a collection of the given size.""" + if index < 0: + raise ArgumentOutOfRangeException__ctor_Z384F8060("index", "Non-negative number required.") + if count < 0: + raise ArgumentOutOfRangeException__ctor_Z384F8060(count_name, "Non-negative number required.") + if size - index < count: + raise ArgumentException__ctor_Z721C83C5( + "Offset and length were out of bounds for the array or count is greater than " + + "the number of elements from index to the end of the source collection." + ) + + +def _binary_search[T](array: Sequence[T], index: int, length: int, value: T, comparer: IComparer_1[T]) -> int: + """Search array[index:index + length] in .NET's probe order. Returns ~insertion point if not found.""" + lo = index + hi = index + length - 1 + while lo <= hi: + i = lo + ((hi - lo) >> 1) + c = comparer.Compare(array[i], value) + if c == 0: + return int32(i) + if c < 0: + lo = i + 1 + else: + hi = i - 1 + return int32(~lo) + + +def binary_search[T](array: Sequence[T], value: T, comparer: IComparer_1[T]) -> int: + """Binary search a sorted array or ResizeArray (System.Array.BinarySearch).""" + return _binary_search(array, 0, len(array), value, comparer) + + +def binary_search_range[T](array: Sequence[T], index: int, length: int, value: T, comparer: IComparer_1[T]) -> int: + """Binary search a sorted range of an array (System.Array.BinarySearch with index and length).""" + _check_range(len(array), index, length, "length") + return _binary_search(array, index, length, value, comparer) + + +def binary_search_range_count[T](xs: list[T], index: int, count: int, value: T, comparer: IComparer_1[T]) -> int: + """Binary search a sorted range of a ResizeArray (ResizeArray.BinarySearch with index and count).""" + _check_range(len(xs), index, count, "count") + return _binary_search(xs, index, count, value, comparer) + + def insert_range_in_place[T](index: int, items: Iterable[T] | IEnumerable_1[T], xs: list[T]) -> None: """Insert a range of items into xs at the given index.""" xs[index:index] = to_iterable(items) @@ -141,6 +188,9 @@ def contains[T](value: T, xs: list[T], cons: Any | None = None) -> bool: "add_in_place", "add_range", "add_range_in_place", + "binary_search", + "binary_search_range", + "binary_search_range_count", "contains", "exists", "filter", diff --git a/tests/Python/TestArray.fs b/tests/Python/TestArray.fs index 260343908c..432c83273e 100644 --- a/tests/Python/TestArray.fs +++ b/tests/Python/TestArray.fs @@ -516,6 +516,93 @@ let ``test System.Array.IndexOf works with non-primitive types`` () = System.Array.IndexOf(myArray, Dog 3, 0, 1) |> equal -1 System.Array.IndexOf(myArray, Duck 5, 1) |> equal -1 +[] +let ``test System.Array.BinarySearch works`` () = + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 0) |> equal -1 + System.Array.BinarySearch(xs, 1) |> equal 0 + System.Array.BinarySearch(xs, 2) |> equal -2 + System.Array.BinarySearch(xs, 7) |> equal 3 + System.Array.BinarySearch(xs, 8) |> equal -5 + System.Array.BinarySearch(xs, 10) |> equal -6 + System.Array.BinarySearch(([||]: int[]), 3) |> equal -1 + +[] +let ``test System.Array.BinarySearch works with duplicates`` () = + System.Array.BinarySearch([|2; 2; 2; 2; 2; 2; 2; 2|], 2) |> equal 3 + System.Array.BinarySearch([|1; 2; 2; 2; 2; 3; 4; 5; 6|], 2) |> equal 4 + +[] +let ``test System.Array.BinarySearch works with a range`` () = + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2) |> equal -2 + System.Array.BinarySearch(xs, 1, 3, 9) |> equal -5 + System.Array.BinarySearch(xs, 1, 2, 9) |> equal -4 + System.Array.BinarySearch(xs, 2, 0, 5) |> equal -3 + System.Array.BinarySearch(xs, 5, 0, 5) |> equal -6 + +[] +let ``test System.Array.BinarySearch works with a comparer`` () = + let desc = ComparisonIdentity.FromFunction(fun (a: int) b -> compare b a) + let ys = [|9; 7; 5; 3; 1|] + System.Array.BinarySearch(ys, 3, desc) |> equal 3 + System.Array.BinarySearch(ys, 4, desc) |> equal -4 + System.Array.BinarySearch(ys, 1, 3, 3, desc) |> equal 3 + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 7, (null: System.Collections.Generic.IComparer)) |> equal 3 + +[] +let ``test System.Array.BinarySearch passes the array element to the comparer first`` () = + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + System.Array.BinarySearch(xs, 9, logging) |> equal 4 + List.ofSeq calls |> equal [(5, 9); (7, 9); (9, 9)] + calls.Clear() + System.Array.BinarySearch(xs, 4, logging) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (1, 4); (3, 4)] + +[] +let ``test System.Array.BinarySearch works with other element types`` () = + let strings = [|"apple"; "banana"; "cherry"|] + System.Array.BinarySearch(strings, "banana") |> equal 1 + System.Array.BinarySearch(strings, "blue") |> equal -3 + let floats = [|1.5; 2.5; 3.5|] + System.Array.BinarySearch(floats, 2.5) |> equal 1 + System.Array.BinarySearch(floats, 3.0) |> equal -3 + let longs = [|1L; 5L; 10L|] + System.Array.BinarySearch(longs, 10L) |> equal 2 + System.Array.BinarySearch(longs, 6L) |> equal -3 + let records = + [|{ MainThing = 1; OtherThing = "x" }; { MainThing = 2; OtherThing = "a" }; { MainThing = 2; OtherThing = "b" }|] + System.Array.BinarySearch(records, { MainThing = 2; OtherThing = "b" }) |> equal 2 + System.Array.BinarySearch(records, { MainThing = 1; OtherThing = "z" }) |> equal -2 + let unions = [|Duck 1; Duck 4; Dog 0|] + System.Array.BinarySearch(unions, Dog 0) |> equal 2 + System.Array.BinarySearch(unions, Duck 2) |> equal -2 + +[] +let ``test System.Array.BinarySearch with a range checks its arguments`` () = + let xs = [|1; 3; 5; 7; 9|] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> System.Array.BinarySearch(xs, -1, 2, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, -1, -1, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, 1, -1, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, 3, 3, 5)) |> equal "ArgumentException" + raised (fun () -> System.Array.BinarySearch(xs, 6, 0, 5)) |> equal "ArgumentException" + [] let ``test Array.findIndex works`` () = let xs = [|1.f; 2.f; 3.f; 4.f|] diff --git a/tests/Python/TestResizeArray.fs b/tests/Python/TestResizeArray.fs index dd98f6bf9d..c76d3435b7 100644 --- a/tests/Python/TestResizeArray.fs +++ b/tests/Python/TestResizeArray.fs @@ -193,6 +193,32 @@ let ``test ResizeArray.IndexOf works`` () = li.IndexOf("ab") |> equal 1 li.IndexOf("cd") |> equal -1 +[] +let ``test ResizeArray.BinarySearch works`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + ra.BinarySearch(7) |> equal 3 + ra.BinarySearch(8) |> equal -5 + let desc = ComparisonIdentity.FromFunction(fun (a: int) b -> compare b a) + (ResizeArray [9; 7; 5; 3; 1]).BinarySearch(3, desc) |> equal 3 + ra.BinarySearch(1, 3, 7, System.Collections.Generic.Comparer.Default) |> equal 3 + ra.BinarySearch(1, 2, 8, System.Collections.Generic.Comparer.Default) |> equal -4 + ra.BinarySearch(1, 2, 3, null) |> equal 1 + +[] +let ``test ResizeArray.BinarySearch with a range checks its arguments`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> ra.BinarySearch(-1, 2, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(1, -1, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(3, 3, 3, null)) |> equal "ArgumentException" + [] let ``test ResizeArray.Remove works`` () = let li = ResizeArray<_>() From 7bba16d2d50df9a1e5f4fd5702dcf021341f5b95 Mon Sep 17 00:00:00 2001 From: Leo Conforti Date: Mon, 26 Oct 2026 12:20:29 -0500 Subject: [PATCH 3/6] feat(dart): support System.Array.BinarySearch and ResizeArray.BinarySearch Adds binarySearch and binarySearchRange to the Dart Array library, following .NET's probe order so arrays with duplicates and custom comparers give the same results. The range overloads raise ArgumentOutOfRangeException and ArgumentException like .NET, with binarySearchRangeCount naming the count "count" for ResizeArray.BinarySearch(index, count, item, comparer). Overloads without a comparer, or with a null literal, use the default comparer that ResizeArray.Sort() uses. --- src/Fable.Transforms/Dart/Replacements.fs | 21 ++++++ src/fable-library-dart/Array.fs | 44 ++++++++++++ tests/Dart/src/ArrayTests.fs | 82 +++++++++++++++++++++++ tests/Dart/src/ResizeArrayTests.fs | 32 +++++++++ 4 files changed, 179 insertions(+) diff --git a/src/Fable.Transforms/Dart/Replacements.fs b/src/Fable.Transforms/Dart/Replacements.fs index 65a3c7311e..ef69270fef 100644 --- a/src/Fable.Transforms/Dart/Replacements.fs +++ b/src/Fable.Transforms/Dart/Replacements.fs @@ -1696,6 +1696,25 @@ let seqModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg ) |> Some +/// BinarySearch without a comparer, or with a null one, uses the default comparer, like ResizeArray.Sort() +let binarySearch (com: ICompiler) ctx r t (i: CallInfo) rangeMeth (ar: Expr) args = + let call meth args comparer = + let comparer = + match comparer with + | None + | Some NullConst -> makeComparer com ctx (getElementType ar.Type) + | Some comparer -> comparer + + Helper.LibCall(com, "Array", meth, t, ar :: args @ [ comparer ], genArgs = i.GenericArgs, ?loc = r) + |> Some + + match args with + | [ value ] -> call "binarySearch" [ value ] None + | [ value; comparer ] -> call "binarySearch" [ value ] (Some comparer) + | [ index; length; value ] -> call rangeMeth [ index; length; value ] None + | [ index; length; value; comparer ] -> call rangeMeth [ index; length; value ] (Some comparer) + | _ -> None + let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) = match i.CompiledName, thisArg, args with | ".ctor", _, [] -> makeResizeArray (getElementType t) [] |> Some @@ -1741,6 +1760,7 @@ let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (this Helper.LibCall(com, "Array", "getSubArray", t, [ ar; idx; cnt ], genArgs = i.GenericArgs, ?loc = r) |> Some | "IndexOf", Some ar, args -> Helper.InstanceCall(ar, "indexOf", t, args, ?loc = r) |> Some + | "BinarySearch", Some ar, args -> binarySearch com ctx r t i "binarySearchRangeCount" ar args | "Insert", Some ar, [ idx; arg ] -> Helper.InstanceCall(ar, "insert", t, [ idx; arg ], ?loc = r) |> Some | "InsertRange", Some ar, [ idx; arg ] -> Helper.InstanceCall(ar, "insertAll", t, [ idx; arg ], ?loc = r) |> Some | "RemoveRange", Some ar, [ startIdx; count ] -> @@ -1823,6 +1843,7 @@ let arrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: E | "IndexOf", None, args -> Helper.LibCall(com, "Array", "indexOf", t, args, i.SignatureArgTypes, genArgs = i.GenericArgs, ?loc = r) |> Some + | "BinarySearch", None, ar :: args -> binarySearch com ctx r t i "binarySearchRange" ar args | "GetEnumerator", Some arg, _ -> getEnumerator com r t arg |> Some | _ -> None diff --git a/src/fable-library-dart/Array.fs b/src/fable-library-dart/Array.fs index 447fd3cdb6..e5b61d9c63 100644 --- a/src/fable-library-dart/Array.fs +++ b/src/fable-library-dart/Array.fs @@ -411,6 +411,50 @@ let indexOf (array: 'T[]) (item: 'T) (start: int option) (count: int option) : i | Some count when i >= start + count -> -1 | _ -> i +// Follows .NET's probe order, so duplicates and comparers with side effects give the same results +let private binarySearchFrom (array: 'T[]) (index: int) (length: int) (value: 'T) (comparer: IComparer<'T>) : int = + let rec loop lo hi = + if lo > hi then + ~~~lo + else + let i = lo + ((hi - lo) >>> 1) + let c = comparer.Compare(array[i], value) + + if c = 0 then + i + elif c < 0 then + loop (i + 1) hi + else + loop lo (i - 1) + + loop index (index + length - 1) + +let private checkBinarySearchRange (lengthName: string) (array: 'T[]) (index: int) (length: int) : unit = + if index < 0 then + raise (System.ArgumentOutOfRangeException("index", "Non-negative number required.")) + + if length < 0 then + raise (System.ArgumentOutOfRangeException(lengthName, "Non-negative number required.")) + + if array.Length - index < length then + raise ( + System.ArgumentException( + "Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection." + ) + ) + +let binarySearch (array: 'T[]) (value: 'T) (comparer: IComparer<'T>) : int = + binarySearchFrom array 0 array.Length value comparer + +let binarySearchRange (array: 'T[]) (index: int) (length: int) (value: 'T) (comparer: IComparer<'T>) : int = + checkBinarySearchRange "length" array index length + binarySearchFrom array index length value comparer + +// ResizeArray's range overload, which names its length "count" +let binarySearchRangeCount (array: 'T[]) (index: int) (count: int) (value: 'T) (comparer: IComparer<'T>) : int = + checkBinarySearchRange "count" array index count + binarySearchFrom array index count value comparer + let partition (f: 'T -> bool) (source: 'T[]) = let res1 = ResizeArray() let res2 = ResizeArray() diff --git a/tests/Dart/src/ArrayTests.fs b/tests/Dart/src/ArrayTests.fs index 54aaa76232..42d953e5f1 100644 --- a/tests/Dart/src/ArrayTests.fs +++ b/tests/Dart/src/ArrayTests.fs @@ -36,6 +36,16 @@ type Things = type Animal = Duck of int | Dog of int +type DescendingComparer() = + interface System.Collections.Generic.IComparer with + member _.Compare(x, y) = compare y x + +type LoggingComparer(calls: ResizeArray) = + interface System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y + let tests () = testCase "Pattern matching with arrays works" <| fun () -> match [||] with [||] -> true | _ -> false @@ -440,6 +450,78 @@ let tests () = // System.Array.IndexOf(myArray, Dog 3, 0, 1) |> equal -1 // System.Array.IndexOf(myArray, Duck 5, 1) |> equal -1 + testCase "System.Array.BinarySearch works" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 0) |> equal -1 + System.Array.BinarySearch(xs, 1) |> equal 0 + System.Array.BinarySearch(xs, 2) |> equal -2 + System.Array.BinarySearch(xs, 7) |> equal 3 + System.Array.BinarySearch(xs, 8) |> equal -5 + System.Array.BinarySearch(xs, 10) |> equal -6 + System.Array.BinarySearch(([||]: int[]), 3) |> equal -1 + + testCase "System.Array.BinarySearch works with duplicates" <| fun () -> + System.Array.BinarySearch([|2; 2; 2; 2; 2; 2; 2; 2|], 2) |> equal 3 + System.Array.BinarySearch([|1; 2; 2; 2; 2; 3; 4; 5; 6|], 2) |> equal 4 + + testCase "System.Array.BinarySearch works with a range" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2) |> equal -2 + System.Array.BinarySearch(xs, 1, 3, 9) |> equal -5 + System.Array.BinarySearch(xs, 1, 2, 9) |> equal -4 + System.Array.BinarySearch(xs, 2, 0, 5) |> equal -3 + System.Array.BinarySearch(xs, 5, 0, 5) |> equal -6 + + testCase "System.Array.BinarySearch works with a comparer" <| fun () -> + let ys = [|9; 7; 5; 3; 1|] + System.Array.BinarySearch(ys, 3, DescendingComparer()) |> equal 3 + System.Array.BinarySearch(ys, 4, DescendingComparer()) |> equal -4 + System.Array.BinarySearch(ys, 1, 3, 3, DescendingComparer()) |> equal 3 + System.Array.BinarySearch([|1; 3; 5; 7; 9|], 7, (null: System.Collections.Generic.IComparer)) |> equal 3 + + testCase "System.Array.BinarySearch passes the array element to the comparer first" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + System.Array.BinarySearch(xs, 9, LoggingComparer(calls)) |> equal 4 + List.ofSeq calls |> equal [(5, 9); (7, 9); (9, 9)] + calls.Clear() + System.Array.BinarySearch(xs, 4, LoggingComparer(calls)) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (1, 4); (3, 4)] + + testCase "System.Array.BinarySearch works with other element types" <| fun () -> + let strings = [|"apple"; "banana"; "cherry"|] + System.Array.BinarySearch(strings, "banana") |> equal 1 + System.Array.BinarySearch(strings, "blue") |> equal -3 + let floats = [|1.5; 2.5; 3.5|] + System.Array.BinarySearch(floats, 2.5) |> equal 1 + System.Array.BinarySearch(floats, 3.0) |> equal -3 + let int64s = [|1L; 5L; 10L|] + System.Array.BinarySearch(int64s, 10L) |> equal 2 + System.Array.BinarySearch(int64s, 6L) |> equal -3 + let things = [|{ MainThing = 1; OtherThing = "x" }; { MainThing = 2; OtherThing = "a" }; { MainThing = 2; OtherThing = "b" }|] + System.Array.BinarySearch(things, { MainThing = 2; OtherThing = "b" }) |> equal 2 + System.Array.BinarySearch(things, { MainThing = 1; OtherThing = "z" }) |> equal -2 + let animals = [|Duck 1; Duck 4; Dog 0|] + System.Array.BinarySearch(animals, Dog 0) |> equal 2 + System.Array.BinarySearch(animals, Duck 2) |> equal -2 + + testCase "System.Array.BinarySearch with a range checks its arguments" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> System.Array.BinarySearch(xs, -1, 2, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, -1, -1, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, 1, -1, 5)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> System.Array.BinarySearch(xs, 3, 3, 5)) |> equal "ArgumentException" + raised (fun () -> System.Array.BinarySearch(xs, 6, 0, 5)) |> equal "ArgumentException" + testCase "Array.findIndex works" <| fun () -> let xs = [|1.f; 2.f; 3.f; 4.f|] xs |> Array.findIndex ((=) 2.f) diff --git a/tests/Dart/src/ResizeArrayTests.fs b/tests/Dart/src/ResizeArrayTests.fs index 706e5f788e..16a75f0b57 100644 --- a/tests/Dart/src/ResizeArrayTests.fs +++ b/tests/Dart/src/ResizeArrayTests.fs @@ -2,6 +2,14 @@ open Util +type AscendingComparer() = + interface System.Collections.Generic.IComparer with + member _.Compare(x, y) = compare x y + +type DescendingComparer() = + interface System.Collections.Generic.IComparer with + member _.Compare(x, y) = compare y x + let tests() = testCase "ResizeArray zero creation works" <| fun () -> let li = ResizeArray() @@ -171,6 +179,30 @@ let tests() = li.IndexOf("ab") |> equal 1 li.IndexOf("cd") |> equal -1 + testCase "ResizeArray.BinarySearch works" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + ra.BinarySearch(7) |> equal 3 + ra.BinarySearch(8) |> equal -5 + (ResizeArray [9; 7; 5; 3; 1]).BinarySearch(3, DescendingComparer()) |> equal 3 + // Comparer.Default isn't supported in Dart + ra.BinarySearch(1, 3, 7, AscendingComparer()) |> equal 3 + ra.BinarySearch(1, 2, 8, AscendingComparer()) |> equal -4 + ra.BinarySearch(1, 2, 3, null) |> equal 1 + + testCase "ResizeArray.BinarySearch with a range checks its arguments" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> ra.BinarySearch(-1, 2, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(1, -1, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(3, 3, 3, null)) |> equal "ArgumentException" + testCase "ResizeArray.Remove works" <| fun () -> let li = ResizeArray<_>() li.Add("ab") From e5a9345f6df10f51478fa6f0f44c4aae098eb1c0 Mon Sep 17 00:00:00 2001 From: Leo Conforti Date: Mon, 26 Oct 2026 12:22:26 -0500 Subject: [PATCH 4/6] feat(beam): support System.Array.BinarySearch and ResizeArray.BinarySearch Both go to new fable_resize_array functions (binary_search, binary_search_range and binary_search_range_count), which search the list as a tuple in .NET's probe order after .NET's range checks. A missing or literal null comparer becomes the comparer Fable uses for compare, so unions follow declaration order. The non-generic System.Array overloads stay unsupported. --- src/Fable.Transforms/Beam/Replacements.fs | 26 ++++++ src/fable-library-beam/fable_resize_array.erl | 54 ++++++++++++ tests/Beam/ArrayTests.fs | 87 +++++++++++++++++++ tests/Beam/ResizeArrayTests.fs | 30 +++++++ 4 files changed, 197 insertions(+) diff --git a/src/Fable.Transforms/Beam/Replacements.fs b/src/Fable.Transforms/Beam/Replacements.fs index 4b3df7f340..f4ba6fe23c 100644 --- a/src/Fable.Transforms/Beam/Replacements.fs +++ b/src/Fable.Transforms/Beam/Replacements.fs @@ -2144,6 +2144,26 @@ let private stringModule let private makeComparer (com: ICompiler) ctx typArg = objExpr [ "Compare", makeComparerFunction com ctx typArg ] +/// BinarySearch of an array or ResizeArray `list`, where a range search goes to `rangeFuncName`. +/// A missing comparer, or a literal null one as .NET allows, means the default comparer. +let private binarySearch (com: ICompiler) ctx r t rangeFuncName (list: Expr) (args: Expr list) = + let call funcName searchArgs (value: Expr) comparer = + let comparer = + match comparer with + | None + | Some NullConst -> makeComparer com ctx value.Type + | Some comparer -> comparer + + Helper.LibCall(com, "fable_resize_array", funcName, t, list :: searchArgs @ [ comparer ], ?loc = r) + |> Some + + match args with + | [ value ] -> call "binary_search" [ value ] value None + | [ value; comparer ] -> call "binary_search" [ value ] value (Some comparer) + | [ index; count; value ] -> call rangeFuncName [ index; count; value ] value None + | [ index; count; value; comparer ] -> call rangeFuncName [ index; count; value ] value (Some comparer) + | _ -> None + let private makeEqualityFunction (com: ICompiler) ctx typArg = let x = makeUniqueIdent com ctx typArg "x" let y = makeUniqueIdent com ctx typArg "y" @@ -2390,6 +2410,9 @@ let private arrays | "IndexOf", None, [ arr; value ] -> let arr = derefArr r arr Helper.LibCall(com, "fable_list", "index_of_value", t, [ value; arr ]) |> Some + // System.Array.BinarySearch, with or without a range and a comparer + | "BinarySearch", None, (ExprType(Array _) as arr) :: args -> + binarySearch com _ctx r t "binary_search_range" (derefArr r arr) args // Iterating an array as a non-generic IEnumerable (e.g. `for v in Enum.GetValues t do`). // Deref via derefArr so byte arrays (atomics, not process-dict refs) are handled too; // the non-generic System.Array of Enum.GetValues stays a ref, which get_enumerator accepts. @@ -3700,6 +3723,9 @@ let private resizeArrays ?loc = r ) |> Some + // BinarySearch + | "BinarySearch", Some callee, args -> + binarySearch com _ctx r t "binary_search_range_count" (emitExpr r (List Any) [ callee ] "get($0)") args // Remove | "Remove", Some callee, [ arg ] -> let listExpr = emitExpr r (List Any) [ callee ] "get($0)" diff --git a/src/fable-library-beam/fable_resize_array.erl b/src/fable-library-beam/fable_resize_array.erl index ec594f894c..c24a6df568 100644 --- a/src/fable-library-beam/fable_resize_array.erl +++ b/src/fable-library-beam/fable_resize_array.erl @@ -9,6 +9,9 @@ insert_range/3, get_range/3, index_of/2, index_of/3, + binary_search/3, + binary_search_range/5, + binary_search_range_count/5, find/3, find_last/2, find_all/2, @@ -32,6 +35,9 @@ -spec get_range(list(), non_neg_integer(), non_neg_integer()) -> list(). -spec index_of(list(), term()) -> integer(). -spec index_of(list(), term(), non_neg_integer()) -> integer(). +-spec binary_search(list(), term(), map() | reference()) -> integer(). +-spec binary_search_range(list(), integer(), integer(), term(), map() | reference()) -> integer(). +-spec binary_search_range_count(list(), integer(), integer(), term(), map() | reference()) -> integer(). -spec find(fun(), list(), term()) -> term(). -spec find_last(list(), fun()) -> term(). -spec find_all(list(), fun()) -> list(). @@ -117,6 +123,54 @@ index_of_impl([], _Item, _Idx) -> -1; index_of_impl([Item | _], Item, Idx) -> Idx; index_of_impl([_ | Rest], Item, Idx) -> index_of_impl(Rest, Item, Idx + 1). +%% BinarySearch with an IComparer, probing in .NET's order so duplicates and comparers get .NET's +%% answers; a miss returns the complement of the index of the first larger element. +binary_search(List, Value, Comparer) -> + Compare = fable_utils:iface_get(compare, Comparer), + binary_search_impl(list_to_tuple(List), 0, length(List) - 1, Value, Compare). + +%% System.Array.BinarySearch(array, index, length, value, comparer) +binary_search_range(List, Index, Length, Value, Comparer) -> + binary_search_range(List, Index, Length, Value, Comparer, <<"length">>). + +%% List.BinarySearch(index, count, item, comparer), whose count .NET calls "count" +binary_search_range_count(List, Index, Count, Item, Comparer) -> + binary_search_range(List, Index, Count, Item, Comparer, <<"count">>). + +%% .NET checks the range in this order before searching it +binary_search_range(_List, Index, _Count, _Value, _Comparer, _CountName) when Index < 0 -> + non_negative_number_required(<<"index">>); +binary_search_range(_List, _Index, Count, _Value, _Comparer, CountName) when Count < 0 -> + non_negative_number_required(CountName); +binary_search_range(List, Index, Count, _Value, _Comparer, _CountName) + when length(List) - Index < Count -> + erlang:error(#{ + exn_type => argument_exception, + message => + <<"Offset and length were out of bounds for the array or count is greater than the " + "number of elements from index to the end of the source collection.">> + }); +binary_search_range(List, Index, Count, Value, Comparer, _CountName) -> + Compare = fable_utils:iface_get(compare, Comparer), + binary_search_impl(list_to_tuple(List), Index, Index + Count - 1, Value, Compare). + +binary_search_impl(Tuple, Lo, Hi, Value, Compare) when Lo =< Hi -> + I = Lo + ((Hi - Lo) bsr 1), + case Compare(element(I + 1, Tuple), Value) of + 0 -> I; + C when C < 0 -> binary_search_impl(Tuple, I + 1, Hi, Value, Compare); + _ -> binary_search_impl(Tuple, Lo, I - 1, Value, Compare) + end; +binary_search_impl(_Tuple, Lo, _Hi, _Value, _Compare) -> + bnot Lo. + +non_negative_number_required(ParamName) -> + erlang:error(#{ + exn_type => argument_out_of_range_exception, + message => + fable_utils:argument_exception_message(<<"Non-negative number required.">>, ParamName) + }). + %% Find first matching predicate, return Default when not found find(_Pred, [], Default) -> Default; diff --git a/tests/Beam/ArrayTests.fs b/tests/Beam/ArrayTests.fs index 9b4544b7d5..eb70f1841e 100644 --- a/tests/Beam/ArrayTests.fs +++ b/tests/Beam/ArrayTests.fs @@ -981,6 +981,93 @@ let ``test System.Array.IndexOf works with non-primitive types`` () = System.Array.IndexOf(myArray, Dog 5) |> equal -1 System.Array.IndexOf(myArray, Duck 5) |> equal 0 +[] +let ``test System.Array.BinarySearch works`` () = + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 0) |> equal -1 + System.Array.BinarySearch(xs, 1) |> equal 0 + System.Array.BinarySearch(xs, 2) |> equal -2 + System.Array.BinarySearch(xs, 7) |> equal 3 + System.Array.BinarySearch(xs, 8) |> equal -5 + System.Array.BinarySearch(xs, 10) |> equal -6 + System.Array.BinarySearch(([||]: int[]), 3) |> equal -1 + +[] +let ``test System.Array.BinarySearch works with duplicates`` () = + System.Array.BinarySearch([|2; 2; 2; 2; 2; 2; 2; 2|], 2) |> equal 3 + System.Array.BinarySearch([|1; 2; 2; 2; 2; 3; 4; 5; 6|], 2) |> equal 4 + +[] +let ``test System.Array.BinarySearch works with a range`` () = + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2) |> equal -2 + System.Array.BinarySearch(xs, 1, 3, 9) |> equal -5 + System.Array.BinarySearch(xs, 1, 2, 9) |> equal -4 + System.Array.BinarySearch(xs, 2, 0, 5) |> equal -3 + System.Array.BinarySearch(xs, 5, 0, 5) |> equal -6 + +[] +let ``test System.Array.BinarySearch works with a comparer`` () = + let desc = { new System.Collections.Generic.IComparer with member _.Compare(a, b) = compare b a } + let ys = [|9; 7; 5; 3; 1|] + System.Array.BinarySearch(ys, 3, desc) |> equal 3 + System.Array.BinarySearch(ys, 4, desc) |> equal -4 + System.Array.BinarySearch(ys, 1, 3, 3, desc) |> equal 3 + System.Array.BinarySearch([|1; 3; 5; 7; 9|], 7, (null: System.Collections.Generic.IComparer)) |> equal 3 + +[] +let ``test System.Array.BinarySearch passes the array element to the comparer first`` () = + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + System.Array.BinarySearch(xs, 9, logging) |> equal 4 + List.ofSeq calls |> equal [(5, 9); (7, 9); (9, 9)] + calls.Clear() + System.Array.BinarySearch(xs, 4, logging) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (1, 4); (3, 4)] + +type BinarySearchRecord = { A: int; B: string } + +[] +let ``test System.Array.BinarySearch works with other element types`` () = + let strings = [|"apple"; "banana"; "cherry"|] + System.Array.BinarySearch(strings, "banana") |> equal 1 + System.Array.BinarySearch(strings, "blue") |> equal -3 + let floats = [|1.5; 2.5; 3.5|] + System.Array.BinarySearch(floats, 2.5) |> equal 1 + System.Array.BinarySearch(floats, 3.0) |> equal -3 + let int64s = [|1L; 5L; 10L|] + System.Array.BinarySearch(int64s, 10L) |> equal 2 + System.Array.BinarySearch(int64s, 6L) |> equal -3 + let records = [|{ A = 1; B = "x" }; { A = 2; B = "a" }; { A = 2; B = "b" }|] + System.Array.BinarySearch(records, { A = 2; B = "b" }) |> equal 2 + System.Array.BinarySearch(records, { A = 1; B = "z" }) |> equal -2 + let unions = [|Duck 1; Duck 4; Dog 0|] + System.Array.BinarySearch(unions, Dog 0) |> equal 2 + System.Array.BinarySearch(unions, Duck 2) |> equal -2 + +[] +let ``test System.Array.BinarySearch with a range checks its arguments`` () = + let xs = [|1; 3; 5; 7; 9|] + let raised index length = + try + System.Array.BinarySearch(xs, index, length, 5) |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised -1 2 |> equal "ArgumentOutOfRangeException" + raised -1 -1 |> equal "ArgumentOutOfRangeException" + raised 1 -1 |> equal "ArgumentOutOfRangeException" + raised 3 3 |> equal "ArgumentException" + raised 6 0 |> equal "ArgumentException" + // --- Additional tests ported from JS --- [] diff --git a/tests/Beam/ResizeArrayTests.fs b/tests/Beam/ResizeArrayTests.fs index 77391ca97f..0b3cc8c1fe 100644 --- a/tests/Beam/ResizeArrayTests.fs +++ b/tests/Beam/ResizeArrayTests.fs @@ -65,6 +65,36 @@ let ``test ResizeArray.IndexOf works`` () = ra.IndexOf(20) |> equal 1 ra.IndexOf(99) |> equal -1 +// --- BinarySearch --- + +[] +let ``test ResizeArray.BinarySearch works`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + let desc = { new IComparer with member _.Compare(a, b) = compare b a } + // Beam has no Comparer.Default, so this stands in for it + let asc = { new IComparer with member _.Compare(a, b) = compare a b } + ra.BinarySearch(7) |> equal 3 + ra.BinarySearch(8) |> equal -5 + (ResizeArray [9; 7; 5; 3; 1]).BinarySearch(3, desc) |> equal 3 + ra.BinarySearch(1, 3, 7, asc) |> equal 3 + ra.BinarySearch(1, 2, 8, asc) |> equal -4 + ra.BinarySearch(1, 2, 3, null) |> equal 1 + +[] +let ``test ResizeArray.BinarySearch with a range checks its arguments`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised index count = + try + ra.BinarySearch(index, count, 3, null) |> ignore + "none" + with + | :? ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? ArgumentException -> "ArgumentException" + | _ -> "other" + raised -1 2 |> equal "ArgumentOutOfRangeException" + raised 1 -1 |> equal "ArgumentOutOfRangeException" + raised 3 3 |> equal "ArgumentException" + // --- Remove --- [] From 587fb848c704b9fad73ca6bd05c57a8d26159829 Mon Sep 17 00:00:00 2001 From: Mangel Maxime Date: Mon, 26 Oct 2026 22:46:34 +0100 Subject: [PATCH 5/6] test(all): cover more BinarySearch scenarios --- tests/Beam/ArrayTests.fs | 47 ++++++++++++++++++++++++++ tests/Beam/ResizeArrayTests.fs | 36 ++++++++++++++++++++ tests/Dart/src/ArrayTests.fs | 42 +++++++++++++++++++++++ tests/Dart/src/ResizeArrayTests.fs | 36 ++++++++++++++++++++ tests/Js/Main/ArrayTests.fs | 49 +++++++++++++++++++++++++++ tests/Js/Main/ResizeArrayTests.fs | 33 ++++++++++++++++++ tests/Python/TestArray.fs | 54 ++++++++++++++++++++++++++++++ tests/Python/TestResizeArray.fs | 36 ++++++++++++++++++++ 8 files changed, 333 insertions(+) diff --git a/tests/Beam/ArrayTests.fs b/tests/Beam/ArrayTests.fs index eb70f1841e..3f9329abff 100644 --- a/tests/Beam/ArrayTests.fs +++ b/tests/Beam/ArrayTests.fs @@ -1051,6 +1051,53 @@ let ``test System.Array.BinarySearch works with other element types`` () = System.Array.BinarySearch(unions, Dog 0) |> equal 2 System.Array.BinarySearch(unions, Duck 2) |> equal -2 +[] +let ``test System.Array.BinarySearch works with a range and a null comparer`` () = + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7, null) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2, null) |> equal -2 + +[] +let ``test System.Array.BinarySearch works with a single element and a large array`` () = + System.Array.BinarySearch([|5|], 5) |> equal 0 + System.Array.BinarySearch([|5|], 3) |> equal -1 + System.Array.BinarySearch([|5|], 7) |> equal -2 + let big = Array.init 1000 (fun i -> i * 2) + System.Array.BinarySearch(big, 998) |> equal 499 + System.Array.BinarySearch(big, 999) |> equal -501 + +[] +let ``test System.Array.BinarySearch works with more element types`` () = + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 3uy) |> equal 1 + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 4uy) |> equal -3 + System.Array.BinarySearch([|-5s; 0s; 5s|], 0s) |> equal 1 + System.Array.BinarySearch([|-5s; 0s; 5s|], -6s) |> equal -1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'c') |> equal 1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'd') |> equal -3 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "b")) |> equal 1 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "c")) |> equal -3 + System.Array.BinarySearch([|1.5M; 2.5M; 3.5M|], 2.5M) |> equal 1 + System.Array.BinarySearch([|1.5M; 2.5M; 3.5M|], 3.0M) |> equal -3 + let dates = [|System.DateTime(2020, 1, 1); System.DateTime(2021, 1, 1)|] + System.Array.BinarySearch(dates, System.DateTime(2021, 1, 1)) |> equal 1 + System.Array.BinarySearch(dates, System.DateTime(2020, 6, 1)) |> equal -2 + System.Array.BinarySearch([|1I; 5I; 10I|], 5I) |> equal 1 + System.Array.BinarySearch([|1I; 5I; 10I|], 11I) |> equal -4 + System.Array.BinarySearch([|None; Some 1; Some 3|], Some 1) |> equal 1 + System.Array.BinarySearch([|None; Some 1; Some 3|], Some 2) |> equal -3 + +[] +let ``test System.Array.BinarySearch with a range passes the array element to the comparer first`` () = + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + System.Array.BinarySearch(xs, 1, 3, 4, logging) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (3, 4)] + [] let ``test System.Array.BinarySearch with a range checks its arguments`` () = let xs = [|1; 3; 5; 7; 9|] diff --git a/tests/Beam/ResizeArrayTests.fs b/tests/Beam/ResizeArrayTests.fs index 0b3cc8c1fe..e8d8c3e7d8 100644 --- a/tests/Beam/ResizeArrayTests.fs +++ b/tests/Beam/ResizeArrayTests.fs @@ -80,6 +80,42 @@ let ``test ResizeArray.BinarySearch works`` () = ra.BinarySearch(1, 2, 8, asc) |> equal -4 ra.BinarySearch(1, 2, 3, null) |> equal 1 +[] +let ``test ResizeArray.BinarySearch works with an empty list, duplicates and a null range comparer`` () = + ResizeArray().BinarySearch(1) |> equal -1 + (ResizeArray [2; 2; 2; 2]).BinarySearch(2) |> equal 1 + (ResizeArray [1; 2; 2; 2; 2; 3]).BinarySearch(2) |> equal 2 + let ra = ResizeArray [1; 3; 5; 7; 9] + ra.BinarySearch(0, 5, 2, null) |> equal -2 + ra.BinarySearch(2, 3, 9, null) |> equal 4 + +[] +let ``test ResizeArray.BinarySearch with a range passes the element to the comparer first`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + ra.BinarySearch(1, 3, 7, logging) |> equal 3 + List.ofSeq calls |> equal [(5, 7); (7, 7)] + +[] +let ``test ResizeArray.BinarySearch with a range checks index and count together`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> ra.BinarySearch(-1, -1, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(6, 0, 3, null)) |> equal "ArgumentException" + raised (fun () -> ra.BinarySearch(0, 6, 3, null)) |> equal "ArgumentException" + [] let ``test ResizeArray.BinarySearch with a range checks its arguments`` () = let ra = ResizeArray [1; 3; 5; 7; 9] diff --git a/tests/Dart/src/ArrayTests.fs b/tests/Dart/src/ArrayTests.fs index 42d953e5f1..e9dfb7b8d2 100644 --- a/tests/Dart/src/ArrayTests.fs +++ b/tests/Dart/src/ArrayTests.fs @@ -506,6 +506,48 @@ let tests () = System.Array.BinarySearch(animals, Dog 0) |> equal 2 System.Array.BinarySearch(animals, Duck 2) |> equal -2 + testCase "System.Array.BinarySearch works with a range and a null comparer" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7, null) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2, null) |> equal -2 + + testCase "System.Array.BinarySearch works with a single element and a large array" <| fun () -> + System.Array.BinarySearch([|5|], 5) |> equal 0 + System.Array.BinarySearch([|5|], 3) |> equal -1 + System.Array.BinarySearch([|5|], 7) |> equal -2 + let big = Array.init 1000 (fun i -> i * 2) + System.Array.BinarySearch(big, 998) |> equal 499 + System.Array.BinarySearch(big, 999) |> equal -501 + + testCase "System.Array.BinarySearch works with more element types" <| fun () -> + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 3uy) |> equal 1 + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 4uy) |> equal -3 + System.Array.BinarySearch([|-5s; 0s; 5s|], 0s) |> equal 1 + System.Array.BinarySearch([|-5s; 0s; 5s|], -6s) |> equal -1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'c') |> equal 1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'd') |> equal -3 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "b")) |> equal 1 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "c")) |> equal -3 + let dates = [|System.DateTime(2020, 1, 1); System.DateTime(2021, 1, 1)|] + System.Array.BinarySearch(dates, System.DateTime(2021, 1, 1)) |> equal 1 + System.Array.BinarySearch(dates, System.DateTime(2020, 6, 1)) |> equal -2 + System.Array.BinarySearch([|None; Some 1; Some 3|], Some 1) |> equal 1 + System.Array.BinarySearch([|None; Some 1; Some 3|], Some 2) |> equal -3 + + testCase "System.Array.BinarySearch with a range passes the array element to the comparer first" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + let logging = + LoggingComparer(calls) + System.Array.BinarySearch(xs, 1, 3, 4, logging) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (3, 4)] + + testCase "System.Array.BinarySearch non-generic overloads work" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs :> System.Array, box 7) |> equal 3 + System.Array.BinarySearch(xs :> System.Array, box 4) |> equal -3 + System.Array.BinarySearch(xs :> System.Array, 1, 3, box 5) |> equal 2 + testCase "System.Array.BinarySearch with a range checks its arguments" <| fun () -> let xs = [|1; 3; 5; 7; 9|] let raised (f: unit -> int) = diff --git a/tests/Dart/src/ResizeArrayTests.fs b/tests/Dart/src/ResizeArrayTests.fs index 16a75f0b57..61788d980c 100644 --- a/tests/Dart/src/ResizeArrayTests.fs +++ b/tests/Dart/src/ResizeArrayTests.fs @@ -10,6 +10,12 @@ type DescendingComparer() = interface System.Collections.Generic.IComparer with member _.Compare(x, y) = compare y x +type LoggingComparer(calls: ResizeArray) = + interface System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y + let tests() = testCase "ResizeArray zero creation works" <| fun () -> let li = ResizeArray() @@ -189,6 +195,36 @@ let tests() = ra.BinarySearch(1, 2, 8, AscendingComparer()) |> equal -4 ra.BinarySearch(1, 2, 3, null) |> equal 1 + testCase "ResizeArray.BinarySearch works with an empty list, duplicates and a null range comparer" <| fun () -> + ResizeArray().BinarySearch(1) |> equal -1 + (ResizeArray [2; 2; 2; 2]).BinarySearch(2) |> equal 1 + (ResizeArray [1; 2; 2; 2; 2; 3]).BinarySearch(2) |> equal 2 + let ra = ResizeArray [1; 3; 5; 7; 9] + ra.BinarySearch(0, 5, 2, null) |> equal -2 + ra.BinarySearch(2, 3, 9, null) |> equal 4 + + testCase "ResizeArray.BinarySearch with a range passes the element to the comparer first" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + let calls = ResizeArray() + let logging = + LoggingComparer(calls) + ra.BinarySearch(1, 3, 7, logging) |> equal 3 + List.ofSeq calls |> equal [(5, 7); (7, 7)] + + testCase "ResizeArray.BinarySearch with a range checks index and count together" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> ra.BinarySearch(-1, -1, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(6, 0, 3, null)) |> equal "ArgumentException" + raised (fun () -> ra.BinarySearch(0, 6, 3, null)) |> equal "ArgumentException" + testCase "ResizeArray.BinarySearch with a range checks its arguments" <| fun () -> let ra = ResizeArray [1; 3; 5; 7; 9] let raised (f: unit -> int) = diff --git a/tests/Js/Main/ArrayTests.fs b/tests/Js/Main/ArrayTests.fs index 6b1537ebe4..4347b44434 100644 --- a/tests/Js/Main/ArrayTests.fs +++ b/tests/Js/Main/ArrayTests.fs @@ -625,6 +625,55 @@ let tests = System.Array.BinarySearch(animals, Dog 0) |> equal 2 System.Array.BinarySearch(animals, Duck 2) |> equal -2 + testCase "System.Array.BinarySearch works with a range and a null comparer" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7, null) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2, null) |> equal -2 + + testCase "System.Array.BinarySearch works with a single element and a large array" <| fun () -> + System.Array.BinarySearch([|5|], 5) |> equal 0 + System.Array.BinarySearch([|5|], 3) |> equal -1 + System.Array.BinarySearch([|5|], 7) |> equal -2 + let big = Array.init 1000 (fun i -> i * 2) + System.Array.BinarySearch(big, 998) |> equal 499 + System.Array.BinarySearch(big, 999) |> equal -501 + + testCase "System.Array.BinarySearch works with more element types" <| fun () -> + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 3uy) |> equal 1 + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 4uy) |> equal -3 + System.Array.BinarySearch([|-5s; 0s; 5s|], 0s) |> equal 1 + System.Array.BinarySearch([|-5s; 0s; 5s|], -6s) |> equal -1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'c') |> equal 1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'd') |> equal -3 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "b")) |> equal 1 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "c")) |> equal -3 + System.Array.BinarySearch([|1.5M; 2.5M; 3.5M|], 2.5M) |> equal 1 + System.Array.BinarySearch([|1.5M; 2.5M; 3.5M|], 3.0M) |> equal -3 + let dates = [|System.DateTime(2020, 1, 1); System.DateTime(2021, 1, 1)|] + System.Array.BinarySearch(dates, System.DateTime(2021, 1, 1)) |> equal 1 + System.Array.BinarySearch(dates, System.DateTime(2020, 6, 1)) |> equal -2 + System.Array.BinarySearch([|1I; 5I; 10I|], 5I) |> equal 1 + System.Array.BinarySearch([|1I; 5I; 10I|], 11I) |> equal -4 + System.Array.BinarySearch([|None; Some 1; Some 3|], Some 1) |> equal 1 + System.Array.BinarySearch([|None; Some 1; Some 3|], Some 2) |> equal -3 + + testCase "System.Array.BinarySearch with a range passes the array element to the comparer first" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + System.Array.BinarySearch(xs, 1, 3, 4, logging) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (3, 4)] + + testCase "System.Array.BinarySearch non-generic overloads work" <| fun () -> + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs :> System.Array, box 7) |> equal 3 + System.Array.BinarySearch(xs :> System.Array, box 4) |> equal -3 + System.Array.BinarySearch(xs :> System.Array, 1, 3, box 5) |> equal 2 + testCase "System.Array.BinarySearch with a range checks its arguments" <| fun () -> let xs = [|1; 3; 5; 7; 9|] let raised (f: unit -> int) = diff --git a/tests/Js/Main/ResizeArrayTests.fs b/tests/Js/Main/ResizeArrayTests.fs index b570150dfc..408699ff97 100644 --- a/tests/Js/Main/ResizeArrayTests.fs +++ b/tests/Js/Main/ResizeArrayTests.fs @@ -293,6 +293,39 @@ let tests = ra.BinarySearch(1, 2, 8, Comparer.Default) |> equal -4 ra.BinarySearch(1, 2, 3, null) |> equal 1 + testCase "ResizeArray.BinarySearch works with an empty list, duplicates and a null range comparer" <| fun () -> + ResizeArray().BinarySearch(1) |> equal -1 + (ResizeArray [2; 2; 2; 2]).BinarySearch(2) |> equal 1 + (ResizeArray [1; 2; 2; 2; 2; 3]).BinarySearch(2) |> equal 2 + let ra = ResizeArray [1; 3; 5; 7; 9] + ra.BinarySearch(0, 5, 2, null) |> equal -2 + ra.BinarySearch(2, 3, 9, null) |> equal 4 + + testCase "ResizeArray.BinarySearch with a range passes the element to the comparer first" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + ra.BinarySearch(1, 3, 7, logging) |> equal 3 + List.ofSeq calls |> equal [(5, 7); (7, 7)] + + testCase "ResizeArray.BinarySearch with a range checks index and count together" <| fun () -> + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> ra.BinarySearch(-1, -1, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(6, 0, 3, null)) |> equal "ArgumentException" + raised (fun () -> ra.BinarySearch(0, 6, 3, null)) |> equal "ArgumentException" + testCase "ResizeArray.BinarySearch with a range checks its arguments" <| fun () -> let ra = ResizeArray [1; 3; 5; 7; 9] let raised (f: unit -> int) = diff --git a/tests/Python/TestArray.fs b/tests/Python/TestArray.fs index 432c83273e..c1f68a3c12 100644 --- a/tests/Python/TestArray.fs +++ b/tests/Python/TestArray.fs @@ -586,6 +586,60 @@ let ``test System.Array.BinarySearch works with other element types`` () = System.Array.BinarySearch(unions, Dog 0) |> equal 2 System.Array.BinarySearch(unions, Duck 2) |> equal -2 +[] +let ``test System.Array.BinarySearch works with a range and a null comparer`` () = + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs, 1, 3, 7, null) |> equal 3 + System.Array.BinarySearch(xs, 1, 3, 2, null) |> equal -2 + +[] +let ``test System.Array.BinarySearch works with a single element and a large array`` () = + System.Array.BinarySearch([|5|], 5) |> equal 0 + System.Array.BinarySearch([|5|], 3) |> equal -1 + System.Array.BinarySearch([|5|], 7) |> equal -2 + let big = Array.init 1000 (fun i -> i * 2) + System.Array.BinarySearch(big, 998) |> equal 499 + System.Array.BinarySearch(big, 999) |> equal -501 + +[] +let ``test System.Array.BinarySearch works with more element types`` () = + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 3uy) |> equal 1 + System.Array.BinarySearch([|1uy; 3uy; 5uy|], 4uy) |> equal -3 + System.Array.BinarySearch([|-5s; 0s; 5s|], 0s) |> equal 1 + System.Array.BinarySearch([|-5s; 0s; 5s|], -6s) |> equal -1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'c') |> equal 1 + System.Array.BinarySearch([|'a'; 'c'; 'e'|], 'd') |> equal -3 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "b")) |> equal 1 + System.Array.BinarySearch([|(1, "a"); (1, "b"); (2, "a")|], (1, "c")) |> equal -3 + System.Array.BinarySearch([|1.5M; 2.5M; 3.5M|], 2.5M) |> equal 1 + System.Array.BinarySearch([|1.5M; 2.5M; 3.5M|], 3.0M) |> equal -3 + let dates = [|System.DateTime(2020, 1, 1); System.DateTime(2021, 1, 1)|] + System.Array.BinarySearch(dates, System.DateTime(2021, 1, 1)) |> equal 1 + System.Array.BinarySearch(dates, System.DateTime(2020, 6, 1)) |> equal -2 + System.Array.BinarySearch([|1I; 5I; 10I|], 5I) |> equal 1 + System.Array.BinarySearch([|1I; 5I; 10I|], 11I) |> equal -4 + System.Array.BinarySearch([|None; Some 1; Some 3|], Some 1) |> equal 1 + System.Array.BinarySearch([|None; Some 1; Some 3|], Some 2) |> equal -3 + +[] +let ``test System.Array.BinarySearch with a range passes the array element to the comparer first`` () = + let xs = [|1; 3; 5; 7; 9|] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + System.Array.BinarySearch(xs, 1, 3, 4, logging) |> equal -3 + List.ofSeq calls |> equal [(5, 4); (3, 4)] + +[] +let ``test System.Array.BinarySearch non-generic overloads work`` () = + let xs = [|1; 3; 5; 7; 9|] + System.Array.BinarySearch(xs :> System.Array, box 7) |> equal 3 + System.Array.BinarySearch(xs :> System.Array, box 4) |> equal -3 + System.Array.BinarySearch(xs :> System.Array, 1, 3, box 5) |> equal 2 + [] let ``test System.Array.BinarySearch with a range checks its arguments`` () = let xs = [|1; 3; 5; 7; 9|] diff --git a/tests/Python/TestResizeArray.fs b/tests/Python/TestResizeArray.fs index c76d3435b7..fe07fc5cb2 100644 --- a/tests/Python/TestResizeArray.fs +++ b/tests/Python/TestResizeArray.fs @@ -204,6 +204,42 @@ let ``test ResizeArray.BinarySearch works`` () = ra.BinarySearch(1, 2, 8, System.Collections.Generic.Comparer.Default) |> equal -4 ra.BinarySearch(1, 2, 3, null) |> equal 1 +[] +let ``test ResizeArray.BinarySearch works with an empty list, duplicates and a null range comparer`` () = + ResizeArray().BinarySearch(1) |> equal -1 + (ResizeArray [2; 2; 2; 2]).BinarySearch(2) |> equal 1 + (ResizeArray [1; 2; 2; 2; 2; 3]).BinarySearch(2) |> equal 2 + let ra = ResizeArray [1; 3; 5; 7; 9] + ra.BinarySearch(0, 5, 2, null) |> equal -2 + ra.BinarySearch(2, 3, 9, null) |> equal 4 + +[] +let ``test ResizeArray.BinarySearch with a range passes the element to the comparer first`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + let calls = ResizeArray() + let logging = + { new System.Collections.Generic.IComparer with + member _.Compare(x, y) = + calls.Add((x, y)) + compare x y } + ra.BinarySearch(1, 3, 7, logging) |> equal 3 + List.ofSeq calls |> equal [(5, 7); (7, 7)] + +[] +let ``test ResizeArray.BinarySearch with a range checks index and count together`` () = + let ra = ResizeArray [1; 3; 5; 7; 9] + let raised (f: unit -> int) = + try + f () |> ignore + "none" + with + | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentException -> "ArgumentException" + | _ -> "other" + raised (fun () -> ra.BinarySearch(-1, -1, 3, null)) |> equal "ArgumentOutOfRangeException" + raised (fun () -> ra.BinarySearch(6, 0, 3, null)) |> equal "ArgumentException" + raised (fun () -> ra.BinarySearch(0, 6, 3, null)) |> equal "ArgumentException" + [] let ``test ResizeArray.BinarySearch with a range checks its arguments`` () = let ra = ResizeArray [1; 3; 5; 7; 9] From 643c3d1ac4da88f1a046e4a56a0d6dff9a9b7ddb Mon Sep 17 00:00:00 2001 From: Mangel Maxime Date: Tue, 27 Oct 2026 09:54:32 +0100 Subject: [PATCH 6/6] test(dart): drop option case from BinarySearch test --- tests/Dart/src/ArrayTests.fs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Dart/src/ArrayTests.fs b/tests/Dart/src/ArrayTests.fs index e9dfb7b8d2..132b255618 100644 --- a/tests/Dart/src/ArrayTests.fs +++ b/tests/Dart/src/ArrayTests.fs @@ -531,8 +531,6 @@ let tests () = let dates = [|System.DateTime(2020, 1, 1); System.DateTime(2021, 1, 1)|] System.Array.BinarySearch(dates, System.DateTime(2021, 1, 1)) |> equal 1 System.Array.BinarySearch(dates, System.DateTime(2020, 6, 1)) |> equal -2 - System.Array.BinarySearch([|None; Some 1; Some 3|], Some 1) |> equal 1 - System.Array.BinarySearch([|None; Some 1; Some 3|], Some 2) |> equal -3 testCase "System.Array.BinarySearch with a range passes the array element to the comparer first" <| fun () -> let xs = [|1; 3; 5; 7; 9|]