From 019b9aabf80069a8d4b7129ab19d2eddd189f46f Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Thu, 25 Jun 2026 21:22:50 +0200 Subject: [PATCH] feat(builtins): make TextIOWrapper enumerable and readline arg optional Resolves #131. - Add `inherit IEnumerable` to TextIOWrapper so file objects can be iterated line-by-line in a type-safe `for line in reader` loop without a cast. Maps to Python's iterator protocol; a file object is its own iterator. - Add zero-arg `readline` / `readlines` overloads on TextIOBase so the size argument is optional (Python's `readline(size=-1)`), matching the existing two-overload pattern used by `read`. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/stdlib/Builtins.fs | 3 +++ test/TestBuiltins.fs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/stdlib/Builtins.fs b/src/stdlib/Builtins.fs index 194b275..ae0715f 100644 --- a/src/stdlib/Builtins.fs +++ b/src/stdlib/Builtins.fs @@ -12,13 +12,16 @@ type TextIOBase = abstract read: __size: int -> string abstract write: __s: string -> int abstract writelines: __lines: string seq -> unit + abstract readline: unit -> string abstract readline: __size: int -> string + abstract readlines: unit -> string list abstract readlines: __hint: int -> string list abstract tell: unit -> int type TextIOWrapper = inherit IDisposable inherit TextIOBase + inherit IEnumerable module OpenTextMode = [] diff --git a/test/TestBuiltins.fs b/test/TestBuiltins.fs index 5f2117f..7d6fcf9 100644 --- a/test/TestBuiltins.fs +++ b/test/TestBuiltins.fs @@ -20,6 +20,38 @@ let ``test write works`` () = result.Dispose() os.remove tempFile +[] +let ``test TextIOWrapper can be enumerated`` () = + let tempFile = os.path.join (os.path.expanduser "~", ".fable_test_iter.txt") + let writer = builtins.``open`` (tempFile, OpenTextMode.Write) + writer.write "a\nb\nc\n" |> ignore + writer.Dispose() + + let lines = ResizeArray() + let reader = builtins.``open`` (tempFile, OpenTextMode.Read) + + for line in reader do + lines.Add(line.Trim()) + + reader.Dispose() + os.remove tempFile + + lines |> List.ofSeq |> equal [ "a"; "b"; "c" ] + +[] +let ``test readline without argument works`` () = + let tempFile = os.path.join (os.path.expanduser "~", ".fable_test_readline.txt") + let writer = builtins.``open`` (tempFile, OpenTextMode.Write) + writer.write "first\nsecond\n" |> ignore + writer.Dispose() + + let reader = builtins.``open`` (tempFile, OpenTextMode.Read) + let line = reader.readline () + reader.Dispose() + os.remove tempFile + + line.Trim() |> equal "first" + [] let ``test max with two arguments works`` () = builtins.max (3, 5) |> equal 5