From 302db73bc11da39426fea88e6f1e3d2c13f6769b Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Sun, 12 Jul 2026 16:26:31 -0700 Subject: [PATCH] Use SearchValues for qualified-name separator scans Replace the paired `IndexOf("::")` + `IndexOf('.')` (and `LastIndexOf` equivalents) separator scans with a single cached `SearchValues` pass over `{ ':', '.' }`. In a qualified name a `:` only ever appears as part of a `::` pair, so scanning for the single characters `:` and `.` is equivalent to scanning for the `::` and `.` separators, while collapsing two passes into one and letting the shared `SearchValues` instance be created once. Converted sites: - `QualifiedNameComparer.GetHashCode` (`IndexOfAny`) - The three `LogPotentialTypedefRemappings` diagnostic blocks in `PInvokeGenerator.cs` (`LastIndexOfAny`) Single-character scans (e.g. `IndexOf('*')`, `Split('\n')`, `Split(["::"])` on a substring) are left as-is since `SearchValues` offers no benefit. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../PInvokeGenerator.cs | 32 +++---------------- .../QualifiedNameComparer.cs | 22 ++++++------- 2 files changed, 15 insertions(+), 39 deletions(-) diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs index f953ba3b..46662b18 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -37,6 +38,7 @@ public sealed partial class PInvokeGenerator : IDisposable private static readonly Encoding s_defaultStreamWriterEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private static readonly string[] s_doubleColonSeparator = ["::"]; private static readonly char[] s_doubleQuoteSeparator = ['"']; + private static readonly SearchValues s_qualifiedNameSeparatorChars = SearchValues.Create(".:"); private static string ExpectedClangVersion => $"version {clang.MajorVersion}.{clang.MinorVersion}"; private static string ExpectedClangSharpVersion => ExpectedClangVersion; // change if necessary @@ -1773,15 +1775,7 @@ public void GenerateBindings(TranslationUnit translationUnit, string filePath, s var addDiag = true; var smlName = name; - var lastSeparatorIndex = smlName.LastIndexOf("::", StringComparison.Ordinal); - - if (lastSeparatorIndex != -1) - { - smlName = smlName[(lastSeparatorIndex + 2)..]; - addDiag = false; - } - - lastSeparatorIndex = smlName.LastIndexOf('.'); + var lastSeparatorIndex = smlName.LastIndexOfAny(s_qualifiedNameSeparatorChars); if (lastSeparatorIndex != -1) { @@ -1818,15 +1812,7 @@ public void GenerateBindings(TranslationUnit translationUnit, string filePath, s var addDiag = true; var smlName = name; - var lastSeparatorIndex = smlName.LastIndexOf("::", StringComparison.Ordinal); - - if (lastSeparatorIndex != -1) - { - smlName = smlName[(lastSeparatorIndex + 2)..]; - addDiag = false; - } - - lastSeparatorIndex = smlName.LastIndexOf('.'); + var lastSeparatorIndex = smlName.AsSpan().LastIndexOfAny(s_qualifiedNameSeparatorChars); if (lastSeparatorIndex != -1) { @@ -1857,15 +1843,7 @@ public void GenerateBindings(TranslationUnit translationUnit, string filePath, s var addDiag = true; var smlName = name; - var lastSeparatorIndex = smlName.LastIndexOf("::", StringComparison.Ordinal); - - if (lastSeparatorIndex != -1) - { - smlName = smlName[(lastSeparatorIndex + 2)..]; - addDiag = false; - } - - lastSeparatorIndex = smlName.LastIndexOf('.'); + var lastSeparatorIndex = smlName.AsSpan().LastIndexOfAny(s_qualifiedNameSeparatorChars); if (lastSeparatorIndex != -1) { diff --git a/sources/ClangSharp.PInvokeGenerator/QualifiedNameComparer.cs b/sources/ClangSharp.PInvokeGenerator/QualifiedNameComparer.cs index 95e048ce..426423b5 100644 --- a/sources/ClangSharp.PInvokeGenerator/QualifiedNameComparer.cs +++ b/sources/ClangSharp.PInvokeGenerator/QualifiedNameComparer.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -10,6 +11,11 @@ internal class QualifiedNameComparer : IEqualityComparer, IAlternateEqua { public static readonly QualifiedNameComparer Default = new QualifiedNameComparer(); + // A qualified name is separated by either `::` or `.`. A `:` only ever appears as part of a + // `::` pair, so scanning for the single characters `:` and `.` is equivalent to scanning for + // the `::` and `.` separators while allowing a single `IndexOfAny`/`LastIndexOfAny` pass. + private static readonly SearchValues s_separatorChars = SearchValues.Create(".:"); + public string Create(ReadOnlySpan alternate) => alternate.ToString(); public bool Equals(ReadOnlySpan alternate, string other) => Equals(alternate, other.AsSpan()); @@ -68,20 +74,12 @@ public int GetHashCode(ReadOnlySpan alternate) var part = alternate; var separatorLength = 0; - var colonSeparatorIndex = part.IndexOf("::", StringComparison.Ordinal); - - if (colonSeparatorIndex != -1) - { - part = part[..colonSeparatorIndex]; - separatorLength = 2; - } - - var dotSeparatorIndex = part.IndexOf('.'); + var separatorIndex = alternate.IndexOfAny(s_separatorChars); - if (dotSeparatorIndex != -1) + if (separatorIndex != -1) { - part = part[..dotSeparatorIndex]; - separatorLength = 1; + part = alternate[..separatorIndex]; + separatorLength = (alternate[separatorIndex] == ':') ? 2 : 1; } hashCode.Add(string.GetHashCode(part, StringComparison.Ordinal));