Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[JS/TS] Added missing ICollection helpers #3949

Merged
merged 1 commit into from
Nov 8, 2024
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 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
Loading