Skip to content

Commit ae1d058

Browse files
authored
Merge pull request #1739 from fsprojects/repo-assist/improve-nameutils-alloc-fastpath-2026-04-13-5985c2920c3bc39b
[Repo Assist] improve: fast-path allocation checks in niceCamelName, capitalizeFirstLetter, Pluralizer
2 parents 4abfd83 + 40e8750 commit ae1d058

3 files changed

Lines changed: 20 additions & 5 deletions

File tree

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## 8.1.8 - Apr 13 2026
44

5+
- Code: `NameUtils.niceCamelName` now short-circuits with zero allocations when the result already starts with a lower-case letter; the general case now uses `StringBuilder.Append(string, int, int)` to avoid creating an intermediate `Substring`. `capitalizeFirstLetter` similarly short-circuits when the first letter is already upper-case. Also fixes `Pluralizer` to avoid `Substring(0,1)` in its capitalize path.
56
- Performance: JSON parser avoids one `Substring` allocation per `\uXXXX` escape by directly indexing into the source string; also uses span-based `Decimal.TryParse`/`Double.TryParse` for number tokens on .NET 8 to avoid a `Substring` allocation per number
67

78
## 8.1.7 - Apr 7 2026

src/FSharp.Data.Runtime.Utilities/NameUtils.fs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,15 @@ let nicePascalName (s: string) =
6767
let niceCamelName (s: string) =
6868
let name = nicePascalName s
6969

70-
if name.Length > 0 then
71-
name.[0].ToString().ToLowerInvariant() + name.Substring(1)
72-
else
70+
if name.Length = 0 || Char.IsLower(name.[0]) then
71+
// Fast path: already starts with a lower-case letter — no allocation needed.
7372
name
73+
else
74+
// Lower-case the first character and append the remainder.
75+
let sb = Text.StringBuilder(name.Length)
76+
sb.Append(Char.ToLowerInvariant(name.[0])) |> ignore
77+
sb.Append(name, 1, name.Length - 1) |> ignore
78+
sb.ToString()
7479

7580
/// Given a function to format names (such as 'niceCamelName' or 'nicePascalName')
7681
/// returns a name generator that never returns duplicate name (by appending an
@@ -115,8 +120,17 @@ let uniqueGenerator (niceName: string -> string) =
115120
let capitalizeFirstLetter (s: string) =
116121
match s.Length with
117122
| 0 -> ""
123+
| _ when Char.IsUpper(s.[0]) ->
124+
// Fast path: already starts with an upper-case letter — no allocation needed.
125+
s
118126
| 1 -> (Char.ToUpperInvariant s.[0]).ToString()
119-
| _ -> (Char.ToUpperInvariant s.[0]).ToString() + s.Substring(1)
127+
| _ ->
128+
// Upper-case the first character and append the remainder via StringBuilder
129+
// to avoid creating two intermediate strings.
130+
let sb = Text.StringBuilder(s.Length)
131+
sb.Append(Char.ToUpperInvariant(s.[0])) |> ignore
132+
sb.Append(s, 1, s.Length - 1) |> ignore
133+
sb.ToString()
120134

121135
/// Trim HTML tags from a given string and replace all of them with spaces
122136
/// Multiple tags are replaced with just a single space. (This is a recursive

src/FSharp.Data.Runtime.Utilities/Pluralizer.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ let private adjustCase (s: string) (template: string) =
226226
else if allUpper then
227227
s.ToUpperInvariant()
228228
else if firstUpper && not <| Char.IsUpper s.[0] then
229-
s.Substring(0, 1).ToUpperInvariant() + s.Substring(1)
229+
(Char.ToUpperInvariant s.[0]).ToString() + s.Substring(1)
230230
else
231231
s
232232

0 commit comments

Comments
 (0)