Skip to content

Commit

Permalink
[JS/TS] Added missing ICollection helpers (#3949)
Browse files Browse the repository at this point in the history
  • Loading branch information
ncave authored Nov 8, 2024
1 parent ce9be9c commit f0bcb6c
Show file tree
Hide file tree
Showing 14 changed files with 1,162 additions and 720 deletions.
1 change: 1 addition & 0 deletions src/Fable.Cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

* [Rust] Fixed derived traits mapping (by @ncave)
* [JS/TS] Added missing ICollection helpers (#3914) (by @ncave)

## 4.23.0 - 2024-10-28

Expand Down
31 changes: 25 additions & 6 deletions src/Fable.Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1790,6 +1790,10 @@ let injectIndexOfArgs com ctx r genArgs args =

injectArg com ctx r "Array" "indexOf" genArgs args

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 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
Expand All @@ -1803,6 +1807,13 @@ let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (this
|> Some
| "get_Item", Some ar, [ idx ] -> getExpr r t ar idx |> Some
| "set_Item", Some ar, [ idx; value ] -> setExpr r ar idx value |> Some
| "CopyTo", Some ar, [ target ] ->
let count = getFieldWith r t ar "length"
copyToArray com r t i [ ar; makeIntConst 0; target; makeIntConst 0; count ]
| "CopyTo", Some ar, [ target; targetIndex ] ->
let count = getFieldWith r t ar "length"
copyToArray com r t i [ ar; makeIntConst 0; target; targetIndex; count ]
| "CopyTo", Some ar, [ _sourceIndex; _target; _targetIndex; _count ] -> copyToArray com r t i (ar :: args)
| "Add", Some ar, [ arg ] ->
"void ($0)"
|> emitExpr r t [ Helper.InstanceCall(ar, "push", t, [ arg ]) ]
Expand Down Expand Up @@ -1942,10 +1953,6 @@ let tuples (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: E
| "ToTuple", _ -> changeKind false args
| _ -> None

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 arrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) =
match i.CompiledName, thisArg, args with
| "get_Length", Some arg, _ -> getFieldWith r t arg "length" |> Some
Expand Down Expand Up @@ -2649,6 +2656,11 @@ let dictionaries (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Exp
makeComparerFromEqualityComparer eqComp
|> makeDictionaryWithComparer com r t arg
|> Some
| [ IEnumerable ], [ arg ] -> makeDictionary com ctx r t arg |> Some
| [ IEnumerable; IEqualityComparer ], [ arg; eqComp ] ->
makeComparerFromEqualityComparer eqComp
|> makeDictionaryWithComparer com r t arg
|> Some
| [ IEqualityComparer ], [ eqComp ]
| [ Number _; IEqualityComparer ], [ _; eqComp ] ->
makeComparerFromEqualityComparer eqComp
Expand Down Expand Up @@ -2680,6 +2692,13 @@ let dictionaries (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Exp
Some c -> Helper.InstanceCall(c, methName, t, args, i.SignatureArgTypes, ?loc = r) |> Some
| _ -> None

let collections (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: Expr option) (args: Expr list) =
match i.CompiledName, thisArg with
| ("get_Count" | "get_IsReadOnly" | "Add" | "Remove" | "Clear" | "Contains" | "CopyTo") as meth, Some ar ->
let meth = Naming.removeGetSetPrefix meth |> Naming.lowerFirst
Helper.LibCall(com, "CollectionUtil", meth, t, ar :: args, ?loc = r) |> Some
| _ -> None

let conditionalWeakTable (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) =
match i.CompiledName, thisArg with
| ".ctor", _ ->
Expand Down Expand Up @@ -3975,8 +3994,8 @@ let private replacedModules =
Types.resizeArray, resizeArrays
"System.Collections.Generic.IList`1", resizeArrays
"System.Collections.IList", resizeArrays
Types.icollectionGeneric, resizeArrays
Types.icollection, resizeArrays
Types.icollectionGeneric, collections
Types.icollection, collections
"System.Collections.Generic.CollectionExtensions", collectionExtensions
"System.ReadOnlySpan`1", readOnlySpans
Types.hashset, hashSets
Expand Down
134 changes: 134 additions & 0 deletions src/fable-library-ts/CollectionUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { equals, isArrayLike } from "./Util.js";

export function count<T>(col: Iterable<T>): number {
if (typeof (col as any)["System.Collections.Generic.ICollection`1.get_Count"] === "function") {
return (col as any)["System.Collections.Generic.ICollection`1.get_Count"](); // collection
} else {
if (isArrayLike(col)) {
return col.length; // resize array
} else {
if (typeof (col as any).size === "number") {
return (col as any).size; // map, set
} else {
let count = 0;
for (const _ of col) {
count++;
}
return count;
}
}
}
}

export function isReadOnly<T>(col: Iterable<T>): boolean {
if (typeof (col as any)["System.Collections.Generic.ICollection`1.get_IsReadOnly"] === "function") {
return (col as any)["System.Collections.Generic.ICollection`1.get_IsReadOnly"](); // collection
} else {
return false;
}
}

export function copyTo<T>(col: Iterable<T>, array: T[], arrayIndex: number) {
if (typeof (col as any)["System.Collections.Generic.ICollection`1.CopyToZ3B4C077E"] === "function") {
(col as any)["System.Collections.Generic.ICollection`1.CopyToZ3B4C077E"](array, arrayIndex); // collection
} else {
let i = arrayIndex;
for (const v of col) {
array[i] = v;
i++;
}
}
}

export function contains<T>(col: Iterable<T>, item: T): boolean {
if (typeof (col as any)["System.Collections.Generic.ICollection`1.Contains2B595"] === "function") {
return (col as any)["System.Collections.Generic.ICollection`1.Contains2B595"](item); // collection
} else {
if (isArrayLike(col)) {
let i = col.findIndex(x => equals(x, item)); // resize array
return i >= 0;
} else {
if (typeof (col as any).has === "function") {
if (typeof (col as any).set === "function" && isArrayLike(item)) {
return (col as any).has(item[0]) && equals((col as any).get(item[0]), item[1]); // map
} else {
return (col as any).has(item); // set
}
} else {
return false; // unknown collection
}
}
}
}

export function add<T>(col: Iterable<T>, item: T): void {
if (typeof (col as any)["System.Collections.Generic.ICollection`1.Add2B595"] === "function") {
return (col as any)["System.Collections.Generic.ICollection`1.Add2B595"](item); // collection
} else {
if (isArrayLike(col)) {
col.push(item); // resize array
} else {
if (typeof (col as any).add === "function") {
return (col as any).add(item); // set
} else {
if (typeof (col as any).has === "function"
&& typeof (col as any).set === "function"
&& isArrayLike(item)) {
if ((col as any).has(item[0]) === false) {
(col as any).set(item[0], item[1]); // map
} else {
throw new Error("An item with the same key has already been added. Key: " + item[0]);
}
} else {
// unknown collection
}
}
}
}
}

export function remove<T>(col: Iterable<T>, item: T): boolean {
if (typeof (col as any)["System.Collections.Generic.ICollection`1.Remove2B595"] === "function") {
return (col as any)["System.Collections.Generic.ICollection`1.Remove2B595"](item); // collection
} else {
if (isArrayLike(col)) {
let i = col.findIndex(x => equals(x, item));
if (i >= 0) {
col.splice(i, 1); // resize array
return true;
} else {
return false;
}
} else {
if (typeof (col as any).delete === "function") {
if (typeof (col as any).set === "function" && isArrayLike(item)) {
if ((col as any).has(item[0]) && equals((col as any).get(item[0]), item[1])) {
return (col as any).delete(item[0]); // map
} else {
return false;
}
} else {
return (col as any).delete(item); // set
}
} else {
return false; // unknown collection
}
}
}
}

export function clear<T>(col: Iterable<T>): void {
if (typeof (col as any)["System.Collections.Generic.ICollection`1.Clear"] === "function") {
return (col as any)["System.Collections.Generic.ICollection`1.Clear"](); // collection
} else {
if (isArrayLike(col)) {
col.splice(0); // resize array
} else {
if (typeof (col as any).clear === "function") {
(col as any).clear(); // map, set
} else {
// unknown collection
}
}
}
}
40 changes: 33 additions & 7 deletions tests/Dart/src/ArrayTests.fs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
module Fable.Tests.Dart.Array

open System
open Util

type ParamArrayTest =
static member Add([<ParamArray>] xs: int[]) = Array.sum xs
static member Add([<System.ParamArray>] xs: int[]) = Array.sum xs

let add (xs: int[]) = ParamArrayTest.Add(xs)

Expand Down Expand Up @@ -312,7 +311,7 @@ let tests () =
// TODO: Char.IsLetter
// testCase "Array.filter with chars works" <| fun () ->
// let xs = [|'a'; '2'; 'b'; 'c'|]
// let ys = xs |> Array.filter Char.IsLetter
// let ys = xs |> Array.filter System.Char.IsLetter
// ys.Length |> equal 3

testCase "Array.find works" <| fun () ->
Expand Down Expand Up @@ -960,16 +959,16 @@ let tests () =
ys :? System.Array |> equal true
zs :? System.Array |> equal false

testCase "Array.Copy works with numeric arrays" <| fun () ->
testCase "System.Array.Copy works with numeric arrays" <| fun () ->
let source = [| 99 |]
let destination = [| 1; 2; 3 |]
Array.Copy(source, 0, destination, 0, 1)
System.Array.Copy(source, 0, destination, 0, 1)
equal [| 99; 2; 3 |] destination

testCase "Array.Copy works with non-numeric arrays" <| fun () ->
testCase "System.Array.Copy works with non-numeric arrays" <| fun () ->
let source = [| "xy"; "xx"; "xyz" |]
let destination = [| "a"; "b"; "c" |]
Array.Copy(source, 1, destination, 1, 2)
System.Array.Copy(source, 1, destination, 1, 2)
equal [| "a"; "xx"; "xyz" |] destination

testCase "Array.splitInto works" <| fun () ->
Expand Down Expand Up @@ -1102,3 +1101,30 @@ let tests () =
throwsAnyError (fun () -> Array.removeManyAt 0 2 [||] |> ignore)
throwsAnyError (fun () -> Array.removeManyAt -1 2 [|1|] |> ignore)
throwsAnyError (fun () -> Array.removeManyAt 2 2 [|1|] |> ignore)

// testCase "Array.compareWith works" <| fun () -> // See #2961
// let a = [|1;3|]
// let b = [|1;2;3|]
// // compares lengths first, then elements
// let c1 = a < b
// let c2 = compare a b
// // should compare elements first, then lengths
// let c3 = Array.compareWith compare a b
// equal c1 true
// equal c2 -1
// equal c3 1

// testCase "System.Array.Resize works" <| fun () ->
// let mutable xs = [|1; 2; 3; 4; 5|]
// System.Array.Resize(&xs, 3)
// xs |> equal [|1; 2; 3|]
// System.Array.Resize(&xs, 7)
// xs |> equal [|1; 2; 3; 0; 0; 0; 0|]
// System.Array.Resize(&xs, 0)
// xs |> equal [||]
// xs <- null
// System.Array.Resize(&xs, 3)
// xs |> equal [|0; 0; 0|]
// xs <- null
// System.Array.Resize(&xs, 0)
// xs |> equal [||]
Loading

0 comments on commit f0bcb6c

Please sign in to comment.