Skip to content

Commit 663c82a

Browse files
github-actions[bot]Copilotdbrattliclaude
authored
fix(stdlib): builtins.map multi-iterable binding + add coverage for untested builtins (#292)
* test(stdlib): add coverage for untested builtins - abs, chr/ord, len, map, type conversions, isinstance, bytes Adds 15 new [<Fact>] tests for builtins that were bound in Builtins.fs but had no test coverage: - abs (int and float) - chr / ord (and round-trip) - len (list and string) - map (single and two-iterable overloads) - str / int / float type conversions - isinstance / type - bytes (from byte array and from string with encoding) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(stdlib): fix len/map cases that hit binding limits `len` on an F# `int list` fails because FSharpList has no `__len__`; convert via `builtins.list` instead. Drop the two-iterable `map` case — the binding's `'T1 * 'T2 -> 'T3` signature compiles to a tuple-input function but Python's `map` calls `f(a, b)` positionally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(stdlib): builtins.map multi-iterable overloads use System.Func The 2- and 3-iterable overloads typed the function as `'T1 * 'T2 -> 'T3` (tuple-input), which Fable compiles to a Python function expecting a single tuple arg. But Python's `map(f, it1, it2)` calls `f(a, b)` positionally, so calls failed with "takes 1 positional argument but 2 were given". Switching to `System.Func<...>` produces a Python function with the correct positional arity. Restores the two-iterable test and adds a three-iterable test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Dag Brattli <dag@brattli.net> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 05f2bea commit 663c82a

2 files changed

Lines changed: 107 additions & 2 deletions

File tree

src/stdlib/Builtins.fs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,12 @@ type IExports =
186186

187187
/// Make an iterator that computes the function using arguments from each
188188
/// of the iterables. Stops when the shortest iterable is exhausted.
189-
abstract map: ('T1 * 'T2 -> 'T3) * IEnumerable<'T1> * IEnumerable<'T2> -> IEnumerable<'T3>
189+
abstract map: System.Func<'T1, 'T2, 'T3> * IEnumerable<'T1> * IEnumerable<'T2> -> IEnumerable<'T3>
190190

191191
/// Make an iterator that computes the function using arguments from each
192192
/// of the iterables. Stops when the shortest iterable is exhausted.
193-
abstract map: ('T1 * 'T2 * 'T3 -> 'T4) * IEnumerable<'T1> * IEnumerable<'T2> * IEnumerable<'T3> -> IEnumerable<'T4>
193+
abstract map:
194+
System.Func<'T1, 'T2, 'T3, 'T4> * IEnumerable<'T1> * IEnumerable<'T2> * IEnumerable<'T3> -> IEnumerable<'T4>
194195

195196
/// Return the Unicode code point for a one-character string.
196197
abstract ord: char -> int

test/TestBuiltins.fs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,107 @@ let ``test pyNone is None`` () =
120120
builtins.bool pyNone |> equal false
121121
// None has type NoneType, so isinstance(None, type(None)) holds
122122
builtins.isinstance (pyNone, builtins.``type`` pyNone) |> equal true
123+
124+
[<Fact>]
125+
let ``test abs with int works`` () =
126+
builtins.abs -5 |> equal 5
127+
builtins.abs 0 |> equal 0
128+
builtins.abs 42 |> equal 42
129+
130+
[<Fact>]
131+
let ``test abs with float works`` () =
132+
builtins.abs -3.14 |> equal 3.14
133+
builtins.abs 0.0 |> equal 0.0
134+
builtins.abs 2.72 |> equal 2.72
135+
136+
[<Fact>]
137+
let ``test chr and ord round-trip works`` () =
138+
builtins.chr 65 |> equal 'A'
139+
builtins.chr 97 |> equal 'a'
140+
builtins.ord 'A' |> equal 65
141+
builtins.ord 'a' |> equal 97
142+
143+
[<Fact>]
144+
let ``test chr ord round-trip preserves value`` () =
145+
let code = 9731 // snowman ☃
146+
builtins.ord (builtins.chr code) |> equal code
147+
148+
[<Fact>]
149+
let ``test len with list works`` () =
150+
// F# `int list` compiles to FSharpList (no __len__); convert via builtins.list.
151+
builtins.len (builtins.list (seq { 1..3 })) |> equal 3
152+
builtins.len (builtins.list Seq.empty<int>) |> equal 0
153+
154+
[<Fact>]
155+
let ``test len with string works`` () =
156+
builtins.len ("hello" |> box) |> equal 5
157+
builtins.len ("" |> box) |> equal 0
158+
159+
[<Fact>]
160+
let ``test map with single iterable works`` () =
161+
builtins.map ((fun x -> x * 2), [ 1; 2; 3 ])
162+
|> Seq.toList
163+
|> equal [ 2; 4; 6 ]
164+
165+
[<Fact>]
166+
let ``test map with two iterables works`` () =
167+
builtins.map ((fun a b -> a + b), [ 1; 2; 3 ], [ 10; 20; 30 ])
168+
|> Seq.toList
169+
|> equal [ 11; 22; 33 ]
170+
171+
[<Fact>]
172+
let ``test map with three iterables works`` () =
173+
builtins.map ((fun a b c -> a + b + c), [ 1; 2; 3 ], [ 10; 20; 30 ], [ 100; 200; 300 ])
174+
|> Seq.toList
175+
|> equal [ 111; 222; 333 ]
176+
177+
[<Fact>]
178+
let ``test str conversion works`` () =
179+
builtins.str 42 |> equal "42"
180+
builtins.str true |> equal "True"
181+
182+
[<Fact>]
183+
let ``test int conversion works`` () =
184+
builtins.int "42" |> equal 42
185+
builtins.int 3.9 |> equal 3
186+
// Python's int() truncates toward zero, not floor
187+
builtins.int -3.9 |> equal -3
188+
189+
[<Fact>]
190+
let ``test float conversion works`` () =
191+
builtins.float "3.14" |> equal 3.14
192+
builtins.float 42 |> equal 42.0
193+
194+
[<Fact>]
195+
let ``test isinstance works`` () =
196+
let pyIntVal: obj = emitPyExpr () "42"
197+
let pyStrVal: obj = emitPyExpr () "'hello'"
198+
builtins.isinstance (pyStrVal, pyStr) |> equal true
199+
builtins.isinstance (pyIntVal, pyInt) |> equal true
200+
201+
[<Fact>]
202+
let ``test isinstance returns false for wrong type`` () =
203+
let pyIntVal: obj = emitPyExpr () "42"
204+
builtins.isinstance (pyIntVal, pyStr) |> equal false
205+
206+
[<Fact>]
207+
let ``test type returns type object`` () =
208+
let pyStrVal: obj = emitPyExpr () "'hello'"
209+
let pyIntVal: obj = emitPyExpr () "42"
210+
let t = builtins.``type`` pyStrVal
211+
builtins.isinstance (pyStrVal, t) |> equal true
212+
builtins.isinstance (pyIntVal, t) |> equal false
213+
214+
[<Fact>]
215+
let ``test bytes from byte array works`` () =
216+
let b = builtins.bytes [| 72uy; 101uy; 108uy; 108uy; 111uy |]
217+
builtins.len b |> equal 5
218+
b.[0] |> equal 72uy
219+
b.[4] |> equal 111uy
220+
221+
[<Fact>]
222+
let ``test bytes from string with encoding works`` () =
223+
let b = builtins.bytes ("ABC", "utf-8")
224+
builtins.len b |> equal 3
225+
b.[0] |> equal 65uy
226+
b.[2] |> equal 67uy

0 commit comments

Comments
 (0)