From 959928cf922428cbae377ff614eae66a7c7ca32a Mon Sep 17 00:00:00 2001 From: Mangel Maxime Date: Thu, 29 Jan 2026 16:06:45 +0100 Subject: [PATCH 1/3] [All] Fix `StringBuilder.Chars` getter and setter --- src/Fable.Cli/CHANGELOG.md | 1 + src/Fable.Compiler/CHANGELOG.md | 1 + src/fable-library-ts/System.Text.fs | 26 +++++++++++++------------- tests/Dart/src/StringTests.fs | 16 ++++++++++++++-- tests/Js/Main/StringTests.fs | 15 +++++++++++++-- tests/Php/TestString.fs | 15 +++++++++++++-- tests/Python/TestString.fs | 15 +++++++++++++-- tests/Rust/tests/src/StringTests.fs | 16 ++++++++++++++-- 8 files changed, 82 insertions(+), 23 deletions(-) diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index f5fd448590..62ae102238 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [Python] Fix type annotations for protocols, ABCs, Atom, and Set module (by @dbrattli) * [Python] Fix type annotations for async functions, date operations, and None handling (by @dbrattli) * [Python] Fix type annotations for tuple indexing, generic defaults, and reflection (by @dbrattli) +* [All] Fix `StringBuilder.Chars` getter and setter (by @MangelMaxime) ### Removed diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md index eaabd3e233..369c5bebe9 100644 --- a/src/Fable.Compiler/CHANGELOG.md +++ b/src/Fable.Compiler/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [Python] Fix type annotations for protocols, ABCs, Atom, and Set module (by @dbrattli) * [Python] Fix type annotations for async functions, date operations, and None handling (by @dbrattli) * [Python] Fix type annotations for tuple indexing, generic defaults, and reflection (by @dbrattli) +* [All] Fix `StringBuilder.Chars` getter and setter (by @MangelMaxime) ## 5.0.0-alpha.21 - 2025-12-26 diff --git a/src/fable-library-ts/System.Text.fs b/src/fable-library-ts/System.Text.fs index c4423570f7..fd1b6f47ea 100644 --- a/src/fable-library-ts/System.Text.fs +++ b/src/fable-library-ts/System.Text.fs @@ -12,11 +12,11 @@ type StringBuilder(value: string, capacity: int) = new() = StringBuilder("", 16) member x.Append(s: string | null) = - buf.Add(string s) + buf.Add(string s) x member x.Append(s: string | null, startIndex: int, count: int) = - buf.Add((string s).Substring(startIndex, count)) + buf.Add((string s).Substring(startIndex, count)) x member x.Append(c: char) = @@ -101,31 +101,31 @@ type StringBuilder(value: string, capacity: int) = with get (index: int) = let mutable len = 0 - let mutable i = -1 + let mutable i = 0 - while i + 1 < buf.Count && len < index do - i <- i + 1 + while i < buf.Count && len + buf[i].Length <= index do len <- len + buf[i].Length + i <- i + 1 - if index < 0 || i < 0 || i >= buf.Count then + if index < 0 || i >= buf.Count then failwith "Index was outside the bounds of the array" else - let pos = len - index - 1 + let pos = index - len buf[i][pos] and set (index: int) (value: char) = let mutable len = 0 - let mutable i = -1 + let mutable i = 0 - while i + 1 < buf.Count && len < index do - i <- i + 1 + while i < buf.Count && len + buf[i].Length <= index do len <- len + buf[i].Length + i <- i + 1 - if index < 0 || i < 0 || i >= buf.Count then + if index < 0 || i >= buf.Count then failwith "Index was outside the bounds of the array" else - let pos = len - index - 1 - buf[i] <- buf[i][0 .. (pos - 1)] + (string value) + buf[i][(pos + 1) ..] + let pos = index - len + buf[i] <- buf[i][0 .. (pos - 1)] + (string value) + buf[i][(pos + 1) ..] member x.Replace(oldValue: char, newValue: char) = for i = buf.Count - 1 downto 0 do diff --git a/tests/Dart/src/StringTests.fs b/tests/Dart/src/StringTests.fs index 9010708743..62f24fa4b7 100644 --- a/tests/Dart/src/StringTests.fs +++ b/tests/Dart/src/StringTests.fs @@ -145,13 +145,20 @@ let tests() = testCase "StringBuilder.Chars works" <| fun () -> let sb = System.Text.StringBuilder() .Append("abc") + .Append("def") + sb.Chars(0) |> equal 'a' sb.Chars(1) |> equal 'b' + sb.Chars(2) |> equal 'c' + sb.Chars(3) |> equal 'd' + sb.Chars(4) |> equal 'e' + sb.Chars(5) |> equal 'f' testCase "StringBuilder.Chars throws when index is out of bounds" <| fun () -> + let sb = System.Text.StringBuilder() + .Append("abc") throwsAnyError <| fun () -> - let sb = System.Text.StringBuilder() - .Append("abc") sb.Chars(-1) |> ignore + throwsAnyError <| fun () -> sb.Chars(3) |> ignore testCase "StringBuilder.Replace works" <| fun () -> @@ -173,6 +180,11 @@ let tests() = sb[1] <- 'x' sb.ToString() |> equal "axc" + throwsAnyError <| fun () -> + sb[-1] <- 'y' + throwsAnyError <| fun () -> + sb[3] <- 'z' + // Formatting // testCase "kprintf works" <| fun () -> diff --git a/tests/Js/Main/StringTests.fs b/tests/Js/Main/StringTests.fs index 17f6e76ae8..659cd386b5 100644 --- a/tests/Js/Main/StringTests.fs +++ b/tests/Js/Main/StringTests.fs @@ -123,13 +123,20 @@ let tests = testList "Strings" [ testCase "StringBuilder.Chars works" <| fun () -> let sb = System.Text.StringBuilder() .Append("abc") + .Append("def") + sb.Chars(0) |> equal 'a' sb.Chars(1) |> equal 'b' + sb.Chars(2) |> equal 'c' + sb.Chars(3) |> equal 'd' + sb.Chars(4) |> equal 'e' + sb.Chars(5) |> equal 'f' testCase "StringBuilder.Chars throws when index is out of bounds" <| fun () -> + let sb = System.Text.StringBuilder() + .Append("abc") throwsAnyError <| fun () -> - let sb = System.Text.StringBuilder() - .Append("abc") sb.Chars(-1) |> ignore + throwsAnyError <| fun () -> sb.Chars(3) |> ignore testCase "StringBuilder.Replace works" <| fun () -> @@ -150,6 +157,10 @@ let tests = testList "Strings" [ .Append("abc") sb[1] <- 'x' sb.ToString() |> equal "axc" + throwsAnyError <| fun () -> + sb[-1] <- 'y' + throwsAnyError <| fun () -> + sb[3] <- 'z' // Formatting diff --git a/tests/Php/TestString.fs b/tests/Php/TestString.fs index 55a37c8e09..7c51597b4a 100644 --- a/tests/Php/TestString.fs +++ b/tests/Php/TestString.fs @@ -218,14 +218,21 @@ let ``StringBuilder.AppendFormat with provider works`` () = let ``StringBuilder.Chars works`` () = let sb = System.Text.StringBuilder() .Append("abc") + .Append("def") + sb.Chars(0) |> equal 'a' sb.Chars(1) |> equal 'b' + sb.Chars(2) |> equal 'c' + sb.Chars(3) |> equal 'd' + sb.Chars(4) |> equal 'e' + sb.Chars(5) |> equal 'f' [] let ``StringBuilder.Chars throws when index is out of bounds`` () = + let sb = System.Text.StringBuilder() + .Append("abc") throwsAnyError <| fun () -> - let sb = System.Text.StringBuilder() - .Append("abc") sb.Chars(-1) |> ignore + throwsAnyError <| fun () -> sb.Chars(3) |> ignore [] @@ -249,6 +256,10 @@ let ``StringBuilder index setter works`` () = .Append("abc") sb[1] <- 'x' sb.ToString() |> equal "axc" + throwsAnyError <| fun () -> + sb[-1] <- 'y' + throwsAnyError <| fun () -> + sb[3] <- 'z' [] let ``test Conversion char to int works`` () = diff --git a/tests/Python/TestString.fs b/tests/Python/TestString.fs index f3942daa2a..e9f7dc4e36 100644 --- a/tests/Python/TestString.fs +++ b/tests/Python/TestString.fs @@ -277,14 +277,21 @@ let ``test StringBuilder.AppendFormat with provider works`` () = let ``test StringBuilder.Chars works`` () = let sb = System.Text.StringBuilder() .Append("abc") + .Append("def") + sb.Chars(0) |> equal 'a' sb.Chars(1) |> equal 'b' + sb.Chars(2) |> equal 'c' + sb.Chars(3) |> equal 'd' + sb.Chars(4) |> equal 'e' + sb.Chars(5) |> equal 'f' [] let ``test StringBuilder.Chars throws when index is out of bounds`` () = + let sb = System.Text.StringBuilder() + .Append("abc") throwsAnyError <| fun () -> - let sb = System.Text.StringBuilder() - .Append("abc") sb.Chars(-1) |> ignore + throwsAnyError <| fun () -> sb.Chars(3) |> ignore [] @@ -308,6 +315,10 @@ let ``test StringBuilder index setter works`` () = .Append("abc") sb[1] <- 'x' equal "axc" (sb.ToString()) + throwsAnyError <| fun () -> + sb[-1] <- 'y' + throwsAnyError <| fun () -> + sb[3] <- 'z' [] let ``test Conversion char to int works`` () = diff --git a/tests/Rust/tests/src/StringTests.fs b/tests/Rust/tests/src/StringTests.fs index cd3fb78c19..8d66db7a9f 100644 --- a/tests/Rust/tests/src/StringTests.fs +++ b/tests/Rust/tests/src/StringTests.fs @@ -135,14 +135,22 @@ let ``StringBuilder.AppendFormat with provider works`` () = let ``StringBuilder.Chars works`` () = let sb = System.Text.StringBuilder() .Append("abc") + .Append("def") + sb.Chars(0) |> equal 'a' sb.Chars(1) |> equal 'b' + sb.Chars(2) |> equal 'c' + sb.Chars(3) |> equal 'd' + sb.Chars(4) |> equal 'e' + sb.Chars(5) |> equal 'f' + [] let ``StringBuilder.Chars throws when index is out of bounds`` () = + let sb = System.Text.StringBuilder() + .Append("abc") throwsAnyError <| fun () -> - let sb = System.Text.StringBuilder() - .Append("abc") sb.Chars(-1) |> ignore + throwsAnyError <| fun () -> sb.Chars(3) |> ignore [] @@ -166,6 +174,10 @@ let ``StringBuilder index setter works`` () = .Append("abc") sb[1] <- 'x' sb.ToString() |> equal "axc" + throwsAnyError <| fun () -> + sb[-1] <- 'y' + throwsAnyError <| fun () -> + sb[3] <- 'z' [] let ``kprintf works`` () = From 10f1453c5ef524f825ae91ab96e4e4b832c8bc59 Mon Sep 17 00:00:00 2001 From: Mangel Maxime Date: Thu, 29 Jan 2026 18:30:05 +0100 Subject: [PATCH 2/3] fix: System.Text.fs StringBuilders for Rust and Dart --- src/fable-library-dart/System.Text.fs | 26 ++++++------ src/fable-library-rust/src/System.Text.fs | 50 +++++++++++------------ 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/fable-library-dart/System.Text.fs b/src/fable-library-dart/System.Text.fs index 217e8ccf37..44c09f4b1a 100644 --- a/src/fable-library-dart/System.Text.fs +++ b/src/fable-library-dart/System.Text.fs @@ -101,35 +101,35 @@ type StringBuilder(value: string, capacity: int) = with get (index: int) = let mutable len = 0 - let mutable i = -1 + let mutable i = 0 - while i + 1 < buf.Count && len < index do - i <- i + 1 + while i < buf.Count && len + buf[i].Length <= index do len <- len + buf[i].Length + i <- i + 1 - if index < 0 || i < 0 || i >= buf.Count then + if index < 0 || i >= buf.Count then failwith "Index was outside the bounds of the array" else - let pos = len - index - 1 + let pos = index - len buf[i][pos] and set (index: int) (value: char) = let mutable len = 0 - let mutable i = -1 + let mutable i = 0 - while i + 1 < buf.Count && len < index do - i <- i + 1 + while i < buf.Count && len + buf[i].Length <= index do len <- len + buf[i].Length + i <- i + 1 - if index < 0 || i < 0 || i >= buf.Count then + if index < 0 || i >= buf.Count then failwith "Index was outside the bounds of the array" else - let pos = len - index - 1 - buf[i] <- buf[i][0 .. (pos - 1)] + (string value) + buf[i][(pos + 1) ..] + let pos = index - len + buf[i] <- buf[i][0 .. (pos - 1)] + (string value) + buf[i][(pos + 1) ..] member x.Replace(oldValue: char, newValue: char) = - let oldValue = string oldValue - let newValue = string newValue + let oldValue = string oldValue + let newValue = string newValue for i = buf.Count - 1 downto 0 do buf[i] <- buf[i].Replace(oldValue, newValue) diff --git a/src/fable-library-rust/src/System.Text.fs b/src/fable-library-rust/src/System.Text.fs index 48ce530610..0735dde387 100644 --- a/src/fable-library-rust/src/System.Text.fs +++ b/src/fable-library-rust/src/System.Text.fs @@ -17,24 +17,24 @@ type StringBuilder(value: string, capacity: int) = buf.Add(s) x - member x.Append(o: bool) = x.Append(string o) - member x.Append(c: char) = x.Append(string c) + member x.Append(o: bool) = x.Append(string o) + member x.Append(c: char) = x.Append(string c) member x.Append(c: char, repeatCount: int) = let s = String.replicate repeatCount (string c) buf.Add(s) x - member x.Append(o: int8) = x.Append(string o) - member x.Append(o: byte) = x.Append(string o) - member x.Append(o: int16) = x.Append(string o) - member x.Append(o: uint16) = x.Append(string o) - member x.Append(o: int32) = x.Append(string o) - member x.Append(o: uint32) = x.Append(string o) - member x.Append(o: int64) = x.Append(string o) - member x.Append(o: uint64) = x.Append(string o) - member x.Append(o: float32) = x.Append(string o) - member x.Append(o: float) = x.Append(string o) + member x.Append(o: int8) = x.Append(string o) + member x.Append(o: byte) = x.Append(string o) + member x.Append(o: int16) = x.Append(string o) + member x.Append(o: uint16) = x.Append(string o) + member x.Append(o: int32) = x.Append(string o) + member x.Append(o: uint32) = x.Append(string o) + member x.Append(o: int64) = x.Append(string o) + member x.Append(o: uint64) = x.Append(string o) + member x.Append(o: float32) = x.Append(string o) + member x.Append(o: float) = x.Append(string o) member x.Append(s: string, index: int, count: int) = x.Append(s.Substring(index, count)) @@ -57,31 +57,31 @@ type StringBuilder(value: string, capacity: int) = with get (index: int) = let mutable len = 0 - let mutable i = -1 + let mutable i = 0 - while i + 1 < buf.Count && len < index do - i <- i + 1 + while i < buf.Count && len + buf[i].Length <= index do len <- len + buf[i].Length + i <- i + 1 - if index < 0 || i < 0 || i >= buf.Count then + if index < 0 || i >= buf.Count then failwith "Index was outside the bounds of the array" else - let pos = len - index - 1 + let pos = index - len buf[i][pos] and set (index: int) (value: char) = let mutable len = 0 - let mutable i = -1 + let mutable i = 0 - while i + 1 < buf.Count && len < index do - i <- i + 1 + while i < buf.Count && len + buf[i].Length <= index do len <- len + buf[i].Length + i <- i + 1 - if index < 0 || i < 0 || i >= buf.Count then + if index < 0 || i >= buf.Count then failwith "Index was outside the bounds of the array" else - let pos = len - index - 1 - buf[i] <- buf[i][0 .. (pos - 1)] + (string value) + buf[i][(pos + 1) ..] + let pos = index - len + buf[i] <- buf[i][0 .. (pos - 1)] + (string value) + buf[i][(pos + 1) ..] member x.Length = let mutable len = 0 @@ -92,8 +92,8 @@ type StringBuilder(value: string, capacity: int) = len member x.Replace(oldValue: char, newValue: char) = - let oldValue = string oldValue - let newValue = string newValue + let oldValue = string oldValue + let newValue = string newValue for i = buf.Count - 1 downto 0 do buf[i] <- buf[i].Replace(oldValue, newValue) From 4cc89349e7cef312eee58bc2500cb2981f7d5cc8 Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Mon, 2 Feb 2026 21:20:35 +0100 Subject: [PATCH 3/3] Fix System.Text.fs for Python --- .../fable_library/System.Text.fs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/fable-library-py/fable_library/System.Text.fs b/src/fable-library-py/fable_library/System.Text.fs index 03d54fbd95..1fa48be009 100644 --- a/src/fable-library-py/fable_library/System.Text.fs +++ b/src/fable-library-py/fable_library/System.Text.fs @@ -15,11 +15,11 @@ type StringBuilder(value: string, capacity: int) = new() = StringBuilder("", 16) member x.Append(s: string | null) = - buf.Add(string s) + buf.Add(string s) x member x.Append(s: string | null, startIndex: int, count: int) = - buf.Add((string s).Substring(startIndex, count)) + buf.Add((string s).Substring(startIndex, count)) x member x.Append(c: char) = @@ -104,31 +104,31 @@ type StringBuilder(value: string, capacity: int) = with get (index: int) = let mutable len = 0 - let mutable i = -1 + let mutable i = 0 - while i + 1 < buf.Count && len < index do - i <- i + 1 + while i < buf.Count && len + buf[i].Length <= index do len <- len + buf[i].Length + i <- i + 1 - if index < 0 || i < 0 || i >= buf.Count then + if index < 0 || i >= buf.Count then failwith "Index was outside the bounds of the array" else - let pos = len - index - 1 + let pos = index - len buf[i][pos] and set (index: int) (value: char) = let mutable len = 0 - let mutable i = -1 + let mutable i = 0 - while i + 1 < buf.Count && len < index do - i <- i + 1 + while i < buf.Count && len + buf[i].Length <= index do len <- len + buf[i].Length + i <- i + 1 - if index < 0 || i < 0 || i >= buf.Count then + if index < 0 || i >= buf.Count then failwith "Index was outside the bounds of the array" else - let pos = len - index - 1 - buf[i] <- buf[i][0 .. (pos - 1)] + (string value) + buf[i][(pos + 1) ..] + let pos = index - len + buf[i] <- buf[i][0 .. (pos - 1)] + (string value) + buf[i][(pos + 1) ..] member x.Replace(oldValue: char, newValue: char) = for i = buf.Count - 1 downto 0 do