From fb40562d011ce2ca9dc8f3dd594a4cbd34c344ae Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Wed, 11 Feb 2026 14:48:56 +0100 Subject: [PATCH] [Python] Fix Array.length/.Length to use len() for plain list interop --- src/Fable.Cli/CHANGELOG.md | 4 ++++ src/Fable.Compiler/CHANGELOG.md | 4 ++++ src/Fable.Transforms/Python/Replacements.fs | 22 +++++++-------------- tests/Python/TestPyInterop.fs | 20 +++++++++++++++++++ 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index de1c60c8b..15a52c9a7 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Fixed + +* [Python] Fix `Array.length`, `.Length`, `Array.isEmpty`, and `ResizeArray.Count` to use `len()` instead of `.length` property for plain Python list interop (by @dbrattli) + ## 5.0.0-alpha.23 - 2026-02-03 ### Changed diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md index 7c91ad783..be2abf868 100644 --- a/src/Fable.Compiler/CHANGELOG.md +++ b/src/Fable.Compiler/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Fixed + +* [Python] Fix `Array.length`, `.Length`, `Array.isEmpty`, and `ResizeArray.Count` to use `len()` instead of `.length` property for plain Python list interop (by @dbrattli) + ## 5.0.0-alpha.22 - 2026-02-03 ### Changed diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index 5260d9ce3..b23b8ac78 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -1760,12 +1760,9 @@ let resizeArrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (this | "GetEnumerator", Some ar, _ -> getEnumerator com r t ar |> Some | "get_Count", Some(MaybeCasted(ar)), _ -> match ar.Type with - // ResizeArray is Python list - use len() wrapped in int32() - | Array(_, ResizeArray) -> + | Array _ -> let lenExpr = Helper.GlobalCall("len", Int32.Number, [ ar ], ?loc = r) Helper.LibCall(com, "core", "int32", t, [ lenExpr ], ?loc = r) |> Some - // MutableArray/ImmutableArray are FSharpArray (Rust) with .length property returning Int32 - | Array _ -> getFieldWith r t ar "length" |> Some | _ -> Helper.LibCall(com, "util", "count", t, [ ar ], ?loc = r) |> Some | "Clear", Some ar, _ -> Helper.LibCall(com, "Util", "clear", t, [ ar ], ?loc = r) |> Some | "Find", Some ar, [ arg ] -> @@ -1894,8 +1891,8 @@ let arrays (com: ICompiler) (ctx: Context) r (t: Type) (i: CallInfo) (thisArg: E // printfn "arrays: %A" i.CompiledName match i.CompiledName, thisArg, args with | "get_Length", Some arg, _ -> - // All arrays in Python are FSharpArray (Rust) which has .length property returning Int32 - getFieldWith r t arg "length" |> Some + let lenExpr = Helper.GlobalCall("len", Int32.Number, [ arg ], ?loc = r) + Helper.LibCall(com, "core", "int32", t, [ lenExpr ], ?loc = r) |> Some | "get_Item", Some arg, [ idx ] -> getExpr r t arg idx |> Some | "set_Item", Some arg, [ idx; value ] -> setExpr r arg idx value |> Some | "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 Helper.LibCall(com, "list", "of_array", t, args, i.SignatureArgTypes, ?loc = r) |> Some | ("Length" | "Count"), [ arg ] -> - match arg.Type with - // ResizeArray is Python list - use len() wrapped in int32() - | Array(_, ResizeArray) -> - let lenExpr = Helper.GlobalCall("len", Int32.Number, [ arg ], ?loc = r) - Helper.LibCall(com, "core", "int32", t, [ lenExpr ], ?loc = r) |> Some - // MutableArray/ImmutableArray are FSharpArray (Rust) with .length property returning Int32 - | _ -> getFieldWith r t arg "length" |> Some + let lenExpr = Helper.GlobalCall("len", Int32.Number, [ arg ], ?loc = r) + Helper.LibCall(com, "core", "int32", t, [ lenExpr ], ?loc = r) |> Some | "Item", [ idx; ar ] -> getExpr r t ar idx |> Some | "Get", [ ar; idx ] -> getExpr r t ar idx |> Some | "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 // Use library function to create empty FSharpArray (Rust) instead of raw Python list Helper.LibCall(com, "array", "empty", t, [], ?loc = r) |> Some | "IsEmpty", [ ar ] -> - // Use .length property (Int32) instead of len() which returns Python int - eq (getFieldWith r Int32.Number ar "length") (makeIntConst 0) |> Some + eq (Helper.GlobalCall("len", Int32.Number, [ ar ], ?loc = r)) (makeIntConst 0) + |> Some | "Concat", [ ar1; ar2 ] -> makeBinOp r t ar1 ar2 BinaryPlus |> Some | Patterns.DicContains nativeArrayFunctions meth, _ -> let args, thisArg = List.splitLast args diff --git a/tests/Python/TestPyInterop.fs b/tests/Python/TestPyInterop.fs index afd747a7e..2eed51854 100644 --- a/tests/Python/TestPyInterop.fs +++ b/tests/Python/TestPyInterop.fs @@ -902,4 +902,24 @@ let ``test Pydantic model_dump_json with float array`` () = json.Contains("1.5") |> equal true json.Contains("2.5") |> equal true +// Regression tests: Array.length/.Length/Array.isEmpty must use len() so they +// work on plain Python lists (e.g. from Emit, unbox, native APIs), not just FSharpArray. + +[] +let ``test Array.length works on plain Python list`` () = + let xs: int[] = emitPyExpr () "[1, 2, 3]" + Array.length xs |> equal 3 + +[] +let ``test .Length works on plain Python list`` () = + let xs: int[] = emitPyExpr () "[10, 20]" + xs.Length |> equal 2 + +[] +let ``test Array.isEmpty works on plain Python list`` () = + let xs: int[] = emitPyExpr () "[1]" + Array.isEmpty xs |> equal false + let ys: int[] = emitPyExpr () "[]" + Array.isEmpty ys |> equal true + #endif