Skip to content

Commit 7883ecc

Browse files
authored
[Python] Fix Array.length/.Length to use len() for plain list interop (#4342)
1 parent e4d4878 commit 7883ecc

4 files changed

Lines changed: 29 additions & 15 deletions

File tree

src/Fable.Cli/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12+
* [Python] Fix `Array.length`, `.Length`, `Array.isEmpty`, and `ResizeArray.Count` to use `len()` instead of `.length` property for plain Python list interop (by @dbrattli)
1213
* [Python] Fix `Task<T>` pass-through returns not being awaited in if/else and try/with branches (by @dbrattli)
1314
* [Python] Fix `:? T as x` type test pattern in closures causing `UnboundLocalError` due to `cast()` shadowing outer variable (by @dbrattli)
1415

src/Fable.Compiler/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12+
* [Python] Fix `Array.length`, `.Length`, `Array.isEmpty`, and `ResizeArray.Count` to use `len()` instead of `.length` property for plain Python list interop (by @dbrattli)
1213
* [Python] Fix `Task<T>` pass-through returns not being awaited in if/else and try/with branches (by @dbrattli)
1314
* [Python] Fix `:? T as x` type test pattern in closures causing `UnboundLocalError` due to `cast()` shadowing outer variable (by @dbrattli)
1415

src/Fable.Transforms/Python/Replacements.fs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1760,12 +1760,9 @@ let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (this
17601760
| "GetEnumerator", Some ar, _ -> getEnumerator com r t ar |> Some
17611761
| "get_Count", Some(MaybeCasted(ar)), _ ->
17621762
match ar.Type with
1763-
// ResizeArray is Python list - use len() wrapped in int32()
1764-
| Array(_, ResizeArray) ->
1763+
| Array _ ->
17651764
let lenExpr = Helper.GlobalCall("len", Int32.Number, [ ar ], ?loc = r)
17661765
Helper.LibCall(com, "core", "int32", t, [ lenExpr ], ?loc = r) |> Some
1767-
// MutableArray/ImmutableArray are FSharpArray (Rust) with .length property returning Int32
1768-
| Array _ -> getFieldWith r t ar "length" |> Some
17691766
| _ -> Helper.LibCall(com, "util", "count", t, [ ar ], ?loc = r) |> Some
17701767
| "Clear", Some ar, _ -> Helper.LibCall(com, "Util", "clear", t, [ ar ], ?loc = r) |> Some
17711768
| "Find", Some ar, [ arg ] ->
@@ -1894,8 +1891,8 @@ let arrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: E
18941891
// printfn "arrays: %A" i.CompiledName
18951892
match i.CompiledName, thisArg, args with
18961893
| "get_Length", Some arg, _ ->
1897-
// All arrays in Python are FSharpArray (Rust) which has .length property returning Int32
1898-
getFieldWith r t arg "length" |> Some
1894+
let lenExpr = Helper.GlobalCall("len", Int32.Number, [ arg ], ?loc = r)
1895+
Helper.LibCall(com, "core", "int32", t, [ lenExpr ], ?loc = r) |> Some
18991896
| "get_Item", Some arg, [ idx ] -> getExpr r t arg idx |> Some
19001897
| "set_Item", Some arg, [ idx; value ] -> setExpr r arg idx value |> Some
19011898
| "Copy", None, [ _source; _sourceIndex; _target; _targetIndex; _count ] -> copyToArray com r t i args
@@ -1946,13 +1943,8 @@ let arrayModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Ex
19461943
Helper.LibCall(com, "list", "of_array", t, args, i.SignatureArgTypes, ?loc = r)
19471944
|> Some
19481945
| ("Length" | "Count"), [ arg ] ->
1949-
match arg.Type with
1950-
// ResizeArray is Python list - use len() wrapped in int32()
1951-
| Array(_, ResizeArray) ->
1952-
let lenExpr = Helper.GlobalCall("len", Int32.Number, [ arg ], ?loc = r)
1953-
Helper.LibCall(com, "core", "int32", t, [ lenExpr ], ?loc = r) |> Some
1954-
// MutableArray/ImmutableArray are FSharpArray (Rust) with .length property returning Int32
1955-
| _ -> getFieldWith r t arg "length" |> Some
1946+
let lenExpr = Helper.GlobalCall("len", Int32.Number, [ arg ], ?loc = r)
1947+
Helper.LibCall(com, "core", "int32", t, [ lenExpr ], ?loc = r) |> Some
19561948
| "Item", [ idx; ar ] -> getExpr r t ar idx |> Some
19571949
| "Get", [ ar; idx ] -> getExpr r t ar idx |> Some
19581950
| "Set", [ ar; idx; value ] -> setExpr r ar idx value |> Some
@@ -1969,8 +1961,8 @@ let arrayModule (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (_: Ex
19691961
// Use library function to create empty FSharpArray (Rust) instead of raw Python list
19701962
Helper.LibCall(com, "array", "empty", t, [], ?loc = r) |> Some
19711963
| "IsEmpty", [ ar ] ->
1972-
// Use .length property (Int32) instead of len() which returns Python int
1973-
eq (getFieldWith r Int32.Number ar "length") (makeIntConst 0) |> Some
1964+
eq (Helper.GlobalCall("len", Int32.Number, [ ar ], ?loc = r)) (makeIntConst 0)
1965+
|> Some
19741966
| "Concat", [ ar1; ar2 ] -> makeBinOp r t ar1 ar2 BinaryPlus |> Some
19751967
| Patterns.DicContains nativeArrayFunctions meth, _ ->
19761968
let args, thisArg = List.splitLast args

tests/Python/TestPyInterop.fs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,4 +902,24 @@ let ``test Pydantic model_dump_json with float array`` () =
902902
json.Contains("1.5") |> equal true
903903
json.Contains("2.5") |> equal true
904904

905+
// Regression tests: Array.length/.Length/Array.isEmpty must use len() so they
906+
// work on plain Python lists (e.g. from Emit, unbox, native APIs), not just FSharpArray.
907+
908+
[<Fact>]
909+
let ``test Array.length works on plain Python list`` () =
910+
let xs: int[] = emitPyExpr () "[1, 2, 3]"
911+
Array.length xs |> equal 3
912+
913+
[<Fact>]
914+
let ``test .Length works on plain Python list`` () =
915+
let xs: int[] = emitPyExpr () "[10, 20]"
916+
xs.Length |> equal 2
917+
918+
[<Fact>]
919+
let ``test Array.isEmpty works on plain Python list`` () =
920+
let xs: int[] = emitPyExpr () "[1]"
921+
Array.isEmpty xs |> equal false
922+
let ys: int[] = emitPyExpr () "[]"
923+
Array.isEmpty ys |> equal true
924+
905925
#endif

0 commit comments

Comments
 (0)