Skip to content

[Repo Assist] improve: fast-path allocation checks in niceCamelName, capitalizeFirstLetter, Pluralizer#1739

Open
github-actions[bot] wants to merge 3 commits intomainfrom
repo-assist/improve-nameutils-alloc-fastpath-2026-04-13-5985c2920c3bc39b
Open

[Repo Assist] improve: fast-path allocation checks in niceCamelName, capitalizeFirstLetter, Pluralizer#1739
github-actions[bot] wants to merge 3 commits intomainfrom
repo-assist/improve-nameutils-alloc-fastpath-2026-04-13-5985c2920c3bc39b

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

🤖 This is an automated pull request from Repo Assist, an AI assistant for this repository.

Summary

Three small allocation-avoidance improvements in NameUtils.fs and Pluralizer.fs for the no-op cases of niceCamelName, capitalizeFirstLetter, and the Pluralizer capitalize path.

Changes

niceCamelName — short-circuit when already lower-case

nicePascalName almost always returns a PascalCase result (upper first char), but occasionally the input is a single digit, a single lower-case letter, or already camelCase. In those cases the result was unnecessarily rebuilt.

// Before: always creates a 1-char string, optionally a new lower-case string,
// then concatenates with Substring(1) — 2-3 allocations every call
name.[0].ToString().ToLowerInvariant() + name.Substring(1)

// After: zero allocations when already lower-case; otherwise one StringBuilder pass
if name.Length = 0 || Char.IsLower(name.[0]) then name
else
    let sb = Text.StringBuilder(name.Length)
    sb.Append(Char.ToLowerInvariant(name.[0])) |> ignore
    sb.Append(name, 1, name.Length - 1) |> ignore   // no intermediate Substring
    sb.ToString()

StringBuilder.Append(string, startIndex, count) appends a span of an existing string without creating an intermediate Substring.

capitalizeFirstLetter — short-circuit when already upper-case

// Before: always created a 1-char string, then concatenated
| _ -> (Char.ToUpperInvariant s.[0]).ToString() + s.Substring(1)

// After: zero allocations when first char is already upper-case;
// otherwise single StringBuilder pass to avoid two intermediate strings
| _ when Char.IsUpper(s.[0]) -> s
| 1 -> (Char.ToUpperInvariant s.[0]).ToString()
| _ ->
    let sb = Text.StringBuilder(s.Length)
    sb.Append(Char.ToUpperInvariant(s.[0])) |> ignore
    sb.Append(s, 1, s.Length - 1) |> ignore
    sb.ToString()

Pluralizer — consistent capitalize helper

// Before: two allocations: Substring(0,1) + ToUpperInvariant() + rest
s.Substring(0, 1).ToUpperInvariant() + s.Substring(1)

// After: one allocation: char → string + rest (consistent with NameUtils)
(Char.ToUpperInvariant s.[0]).ToString() + s.Substring(1)

Impact

These functions are called at design time for every property and type name generated by the JSON, CSV, and XML type providers. The saving is modest per call but adds up over large schemas. More importantly, the fast-path cases make the intent explicit: "if no change is needed, return the input unchanged."

Test Status

  • dotnet test tests/FSharp.Data.Core.Tests/2920 passed, 0 failed
  • ✅ Fantomas formatting check passed (no changes)

Generated by Repo Assist

Generated by 🌈 Repo Assist, see workflow run. Learn more.

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@97143ac59cb3a13ef2a77581f929f06719c7402a

…tLetter, Pluralizer

- niceCamelName: if nicePascalName result already starts with a lower-case
  letter (empty result is also handled), return it directly with zero
  allocation. Otherwise use StringBuilder.Append(string, startIndex, count)
  to avoid creating an intermediate Substring for the tail.

- capitalizeFirstLetter: if the first char is already upper-case, return
  the input string directly. The length-1 path is unchanged. The general
  case now also uses StringBuilder to avoid two intermediate strings.

- Pluralizer: replace s.Substring(0, 1).ToUpperInvariant() (which allocates
  a 1-char string then a new string for the result) with the simpler
  (Char.ToUpperInvariant s.[0]).ToString() pattern consistent with the
  rest of NameUtils.

Bumps version to 8.1.8.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dsyme dsyme marked this pull request as ready for review April 13, 2026 11:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant