From 9a42c3ee4459996f64c06eca417673bf79c86f0e Mon Sep 17 00:00:00 2001 From: Andrew J Said Date: Wed, 25 Oct 2023 18:56:07 +0100 Subject: [PATCH 1/4] Add failing tests --- .../Frozen/FrozenFromKnownValuesTests.cs | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs index e11de4412941e5..179506b896bdd7 100644 --- a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs +++ b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs @@ -27,6 +27,7 @@ public static IEnumerable StringStringData() => from comparer in new[] { StringComparer.Ordinal, StringComparer.OrdinalIgnoreCase } from keys in new string[][] { + // from https://github.com/dotnet/runtime/blob/a30de6d40f69ef612b514344a5ec83fffd10b957/src/libraries/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs#L84-L327 new[] { @@ -133,14 +134,14 @@ public static IEnumerable StringStringData() => }, // exercise left/right justified ordinal comparers - Enumerable.Range(0, 10).Select(i => $"{i}ABCDEFGH").ToArray(), // left justified single char ascii - Enumerable.Range(0, 10).Select(i => $"ABCDEFGH{i}").ToArray(), // right justified single char ascii - Enumerable.Range(0, 100).Select(i => $"{i:D2}ABCDEFGH").ToArray(), // left justified substring ascii - Enumerable.Range(0, 100).Select(i => $"ABCDEFGH{i:D2}").ToArray(), // right justified substring ascii - Enumerable.Range(0, 10).Select(i => $"{i}ABCDEFGH\U0001F600").ToArray(), // left justified single char non-ascii - Enumerable.Range(0, 10).Select(i => $"ABCDEFGH\U0001F600{i}").ToArray(), // right justified single char non-ascii - Enumerable.Range(0, 100).Select(i => $"{i:D2}ABCDEFGH\U0001F600").ToArray(), // left justified substring non-ascii - Enumerable.Range(0, 100).Select(i => $"ABCDEFGH\U0001F600{i:D2}").ToArray(), // right justified substring non-ascii + Enumerable.Range(0, 10).Select(i => $"{i}ABCDefgh").ToArray(), // left justified single char ascii + Enumerable.Range(0, 10).Select(i => $"ABCDefgh{i}").ToArray(), // right justified single char ascii + Enumerable.Range(0, 100).Select(i => $"{i:D2}ABCDefgh").ToArray(), // left justified substring ascii + Enumerable.Range(0, 100).Select(i => $"ABCDefgh{i:D2}").ToArray(), // right justified substring ascii + Enumerable.Range(0, 10).Select(i => $"{i}ABCDefgh\U0001F600").ToArray(), // left justified single char non-ascii + Enumerable.Range(0, 10).Select(i => $"ABCDefgh\U0001F600{i}").ToArray(), // right justified single char non-ascii + Enumerable.Range(0, 100).Select(i => $"{i:D2}ABCDefgh\U0001F600").ToArray(), // left justified substring non-ascii + Enumerable.Range(0, 100).Select(i => $"ABCDefgh\U0001F600{i:D2}").ToArray(), // right justified substring non-ascii Enumerable.Range(0, 20).Select(i => i.ToString("D2")).Select(s => (char)(s[0] + 128) + "" + (char)(s[1] + 128)).ToArray(), // left-justified non-ascii } select new object[] { keys.ToDictionary(i => i, i => i, comparer) }; @@ -191,6 +192,23 @@ private static void FrozenDictionaryWorker(Dictionary pair in source) + { + var keyUpper = (TKey)(object)((string)(object)pair.Key).ToUpper(); + var isValidTest = frozen.Comparer.Equals(pair.Key, keyUpper); + if (isValidTest) + { + Assert.Equal(pair.Value, frozen.GetValueRefOrNullRef(keyUpper)); + Assert.Equal(pair.Value, frozen[keyUpper]); + Assert.True(frozen.TryGetValue(keyUpper, out TValue value)); + Assert.Equal(pair.Value, value); + } + } + } + foreach (KeyValuePair pair in frozen) { Assert.True(source.TryGetValue(pair.Key, out TValue value)); @@ -201,6 +219,7 @@ private static void FrozenDictionaryWorker(Dictionary(Dictionary source) Assert.True(frozen.TryGetValue(pair.Key, out TKey actualKey)); Assert.Equal(pair.Key, actualKey); } + + if (typeof(TKey) == typeof(string) && ReferenceEquals(frozen.Comparer, StringComparer.OrdinalIgnoreCase)) + { + foreach (KeyValuePair pair in source) + { + var keyUpper = (TKey)(object)((string)(object)pair.Key).ToUpper(); + var isValidTest = frozen.Comparer.Equals(pair.Key, keyUpper); + if (isValidTest) + { + Assert.True(frozen.Contains(keyUpper)); + Assert.True(frozen.TryGetValue(keyUpper, out TKey actualKey)); + Assert.Equal(pair.Key, actualKey); + } + } + } + foreach (TKey key in frozen) { Assert.True(source.TryGetValue(key, out _)); @@ -278,6 +313,7 @@ private void FrozenSetWorker(Dictionary source) { Assert.True(frozen.Contains(key)); } + foreach (TKey item in frozen.Items) { Assert.True(source.ContainsKey(item)); From a94513ce0007fcb8ac5b7ccd9032943ecda65df3 Mon Sep 17 00:00:00 2001 From: Andrew J Said Date: Thu, 26 Oct 2023 14:05:35 +0100 Subject: [PATCH 2/4] Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases fixes #93974 --- .../src/System.Collections.Immutable.csproj | 13 +++- .../Collections/Frozen/FrozenDictionary.cs | 40 +++++++--- .../System/Collections/Frozen/FrozenSet.cs | 40 +++++++--- .../Collections/Frozen/String/KeyAnalyzer.cs | 12 ++- ...lCaseSensitiveHashCaseInsensitiveEquals.cs | 28 +++++++ ..._LeftJustifiedSingleCharCaseInsensitive.cs | 29 +++++++ ...y_LeftJustifiedSubstringCaseInsensitive.cs | 30 ++++++++ ...RightJustifiedSingleCharCaseInsensitive.cs | 29 +++++++ ..._RightJustifiedSubstringCaseInsensitive.cs | 30 ++++++++ ...lCaseSensitiveHashCaseInsensitiveEquals.cs | 27 +++++++ ..._LeftJustifiedSingleCharCaseInsensitive.cs | 28 +++++++ ...t_LeftJustifiedSubstringCaseInsensitive.cs | 29 +++++++ ...RightJustifiedSingleCharCaseInsensitive.cs | 28 +++++++ ..._RightJustifiedSubstringCaseInsensitive.cs | 29 +++++++ .../Frozen/FrozenFromKnownValuesTests.cs | 8 +- .../tests/Frozen/KeyAnalyzerTests.cs | 75 +++++++++++++------ 16 files changed, 424 insertions(+), 51 deletions(-) create mode 100644 src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_FullCaseSensitiveHashCaseInsensitiveEquals.cs create mode 100644 src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_LeftJustifiedSingleCharCaseInsensitive.cs create mode 100644 src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_LeftJustifiedSubstringCaseInsensitive.cs create mode 100644 src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_RightJustifiedSingleCharCaseInsensitive.cs create mode 100644 src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_RightJustifiedSubstringCaseInsensitive.cs create mode 100644 src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_FullCaseSensitiveHashCaseInsensitiveEquals.cs create mode 100644 src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_LeftJustifiedSingleCharCaseInsensitive.cs create mode 100644 src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_LeftJustifiedSubstringCaseInsensitive.cs create mode 100644 src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_RightJustifiedSingleCharCaseInsensitive.cs create mode 100644 src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_RightJustifiedSubstringCaseInsensitive.cs diff --git a/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj b/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj index 5fc208a43a3cbc..33eb902cddff64 100644 --- a/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj +++ b/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj @@ -11,7 +11,7 @@ The System.Collections.Immutable library is built-in as part of the shared frame - + @@ -67,6 +67,17 @@ The System.Collections.Immutable library is built-in as part of the shared frame + + + + + + + + + + + diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.cs index e4fbdcef00b3c6..b0fad1cd1115c3 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.cs @@ -181,7 +181,7 @@ private static FrozenDictionary CreateFromDictionary { if (analysis.RightJustifiedSubstring) { - if (analysis.IgnoreCase) + if (analysis.IgnoreCaseForHash) { frozenDictionary = analysis.AllAsciiIfIgnoreCase ? new OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveAsciiSubstring(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) @@ -189,14 +189,23 @@ private static FrozenDictionary CreateFromDictionary } else { - frozenDictionary = analysis.HashCount == 1 - ? new OrdinalStringFrozenDictionary_RightJustifiedSingleChar(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex) - : new OrdinalStringFrozenDictionary_RightJustifiedSubstring(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); + if (analysis.HashCount == 1) + { + frozenDictionary = analysis.IgnoreCaseForEquals + ? new OrdinalStringFrozenDictionary_RightJustifiedSingleCharCaseInsensitive(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex) + : new OrdinalStringFrozenDictionary_RightJustifiedSingleChar(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex); + } + else + { + frozenDictionary = analysis.IgnoreCaseForEquals + ? new OrdinalStringFrozenDictionary_RightJustifiedSubstringCaseInsensitive(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) + : new OrdinalStringFrozenDictionary_RightJustifiedSubstring(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); + } } } else { - if (analysis.IgnoreCase) + if (analysis.IgnoreCaseForHash) { frozenDictionary = analysis.AllAsciiIfIgnoreCase ? new OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveAsciiSubstring(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) @@ -204,15 +213,24 @@ private static FrozenDictionary CreateFromDictionary } else { - frozenDictionary = analysis.HashCount == 1 - ? new OrdinalStringFrozenDictionary_LeftJustifiedSingleChar(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex) - : new OrdinalStringFrozenDictionary_LeftJustifiedSubstring(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); + if (analysis.HashCount == 1) + { + frozenDictionary = analysis.IgnoreCaseForEquals + ? new OrdinalStringFrozenDictionary_LeftJustifiedSingleCharCaseInsensitive(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex) + : new OrdinalStringFrozenDictionary_LeftJustifiedSingleChar(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex); + } + else + { + frozenDictionary = analysis.IgnoreCaseForEquals + ? new OrdinalStringFrozenDictionary_LeftJustifiedSubstringCaseInsensitive(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) + : new OrdinalStringFrozenDictionary_LeftJustifiedSubstring(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); + } } } } else { - if (analysis.IgnoreCase) + if (analysis.IgnoreCaseForHash) { frozenDictionary = analysis.AllAsciiIfIgnoreCase ? new OrdinalStringFrozenDictionary_FullCaseInsensitiveAscii(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff) @@ -220,7 +238,9 @@ private static FrozenDictionary CreateFromDictionary } else { - frozenDictionary = new OrdinalStringFrozenDictionary_Full(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff); + frozenDictionary = analysis.IgnoreCaseForEquals + ? new OrdinalStringFrozenDictionary_FullCaseSensitiveHashCaseInsensitiveEquals(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff) + : new OrdinalStringFrozenDictionary_Full(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff); } } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs index 8c315f214fe03c..63c2be5dbec1ba 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs @@ -129,7 +129,7 @@ private static FrozenSet CreateFromSet(HashSet source) { if (analysis.RightJustifiedSubstring) { - if (analysis.IgnoreCase) + if (analysis.IgnoreCaseForHash) { frozenSet = analysis.AllAsciiIfIgnoreCase ? new OrdinalStringFrozenSet_RightJustifiedCaseInsensitiveAsciiSubstring(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) @@ -137,14 +137,23 @@ private static FrozenSet CreateFromSet(HashSet source) } else { - frozenSet = analysis.HashCount == 1 - ? new OrdinalStringFrozenSet_RightJustifiedSingleChar(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex) - : new OrdinalStringFrozenSet_RightJustifiedSubstring(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); + if (analysis.HashCount == 1) + { + frozenSet = analysis.IgnoreCaseForEquals + ? new OrdinalStringFrozenSet_RightJustifiedSingleCharCaseInsensitive(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex) + : new OrdinalStringFrozenSet_RightJustifiedSingleChar(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex); ; + } + else + { + frozenSet = analysis.IgnoreCaseForEquals + ? new OrdinalStringFrozenSet_RightJustifiedSubstringCaseInsensitive(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) + : new OrdinalStringFrozenSet_RightJustifiedSubstring(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); + } } } else { - if (analysis.IgnoreCase) + if (analysis.IgnoreCaseForHash) { frozenSet = analysis.AllAsciiIfIgnoreCase ? new OrdinalStringFrozenSet_LeftJustifiedCaseInsensitiveAsciiSubstring(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) @@ -152,15 +161,24 @@ private static FrozenSet CreateFromSet(HashSet source) } else { - frozenSet = analysis.HashCount == 1 - ? new OrdinalStringFrozenSet_LeftJustifiedSingleChar(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex) - : new OrdinalStringFrozenSet_LeftJustifiedSubstring(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); + if (analysis.HashCount == 1) + { + frozenSet = analysis.IgnoreCaseForEquals + ? new OrdinalStringFrozenSet_LeftJustifiedSingleCharCaseInsensitive(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex) + : new OrdinalStringFrozenSet_LeftJustifiedSingleChar(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex); + } + else + { + frozenSet = analysis.IgnoreCaseForEquals + ? new OrdinalStringFrozenSet_LeftJustifiedSubstringCaseInsensitive(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) + : new OrdinalStringFrozenSet_LeftJustifiedSubstring(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); + } } } } else { - if (analysis.IgnoreCase) + if (analysis.IgnoreCaseForHash) { frozenSet = analysis.AllAsciiIfIgnoreCase ? new OrdinalStringFrozenSet_FullCaseInsensitiveAscii(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff) @@ -168,7 +186,9 @@ private static FrozenSet CreateFromSet(HashSet source) } else { - frozenSet = new OrdinalStringFrozenSet_Full(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff); + frozenSet = analysis.IgnoreCaseForEquals + ? new OrdinalStringFrozenSet_FullCaseSensitiveHashCaseInsensitiveEquals(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff) + : new OrdinalStringFrozenSet_Full(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff); } } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/KeyAnalyzer.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/KeyAnalyzer.cs index f6907367d8b9a2..9fb6f4ad4258e7 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/KeyAnalyzer.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/KeyAnalyzer.cs @@ -120,6 +120,8 @@ private static AnalysisResults CreateAnalysisResults( // Start off by assuming all strings are ASCII bool allAsciiIfIgnoreCase = true; + bool ignoreCaseForEquals = ignoreCase; + // If we're case-sensitive, it doesn't matter if the strings are ASCII or not. // But if we're case-insensitive, we can switch to a faster comparer if all the // substrings are ASCII, so we check each. @@ -160,7 +162,7 @@ private static AnalysisResults CreateAnalysisResults( } // Return the analysis results. - return new AnalysisResults(ignoreCase, allAsciiIfIgnoreCase, index, count, minLength, maxLength); + return new AnalysisResults(ignoreCase, ignoreCaseForEquals, allAsciiIfIgnoreCase, index, count, minLength, maxLength); } private delegate ReadOnlySpan GetSpan(string s, int index, int count); @@ -243,9 +245,10 @@ internal static bool HasSufficientUniquenessFactor(HashSet set, ReadOnly internal readonly struct AnalysisResults { - public AnalysisResults(bool ignoreCase, bool allAsciiIfIgnoreCase, int hashIndex, int hashCount, int minLength, int maxLength) + public AnalysisResults(bool ignoreCaseForHash, bool ignoreCaseForEquals, bool allAsciiIfIgnoreCase, int hashIndex, int hashCount, int minLength, int maxLength) { - IgnoreCase = ignoreCase; + IgnoreCaseForHash = ignoreCaseForHash; + IgnoreCaseForEquals = ignoreCaseForEquals; AllAsciiIfIgnoreCase = allAsciiIfIgnoreCase; HashIndex = hashIndex; HashCount = hashCount; @@ -253,7 +256,8 @@ public AnalysisResults(bool ignoreCase, bool allAsciiIfIgnoreCase, int hashIndex MaximumLengthDiff = maxLength - minLength; } - public bool IgnoreCase { get; } + public bool IgnoreCaseForHash { get; } + public bool IgnoreCaseForEquals { get; } public bool AllAsciiIfIgnoreCase { get; } public int HashIndex { get; } public int HashCount { get; } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_FullCaseSensitiveHashCaseInsensitiveEquals.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_FullCaseSensitiveHashCaseInsensitiveEquals.cs new file mode 100644 index 00000000000000..f3c805e416054c --- /dev/null +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_FullCaseSensitiveHashCaseInsensitiveEquals.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Collections.Frozen +{ + internal sealed class OrdinalStringFrozenDictionary_FullCaseSensitiveHashCaseInsensitiveEquals : OrdinalStringFrozenDictionary + { + internal OrdinalStringFrozenDictionary_FullCaseSensitiveHashCaseInsensitiveEquals( + string[] keys, + TValue[] values, + IEqualityComparer comparer, + int minimumLength, + int maximumLengthDiff) + : base(keys, values, comparer, minimumLength, maximumLengthDiff) + { + } + + // This override is necessary to force the jit to emit the code in such a way that it + // avoids virtual dispatch overhead when calling the Equals/GetHashCode methods. Don't + // remove this, or you'll tank performance. + private protected override ref readonly TValue GetValueRefOrNullRefCore(string key) => ref base.GetValueRefOrNullRefCore(key); + + private protected override bool Equals(string? x, string? y) => StringComparer.OrdinalIgnoreCase.Equals(x, y); + private protected override int GetHashCode(string s) => Hashing.GetHashCodeOrdinal(s.AsSpan()); + } +} diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_LeftJustifiedSingleCharCaseInsensitive.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_LeftJustifiedSingleCharCaseInsensitive.cs new file mode 100644 index 00000000000000..2bebf7e04c18f0 --- /dev/null +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_LeftJustifiedSingleCharCaseInsensitive.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Collections.Frozen +{ + internal sealed class OrdinalStringFrozenDictionary_LeftJustifiedSingleCharCaseInsensitive : OrdinalStringFrozenDictionary + { + internal OrdinalStringFrozenDictionary_LeftJustifiedSingleCharCaseInsensitive( + string[] keys, + TValue[] values, + IEqualityComparer comparer, + int minimumLength, + int maximumLengthDiff, + int hashIndex) + : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, 1) + { + } + + // This override is necessary to force the jit to emit the code in such a way that it + // avoids virtual dispatch overhead when calling the Equals/GetHashCode methods. Don't + // remove this, or you'll tank performance. + private protected override ref readonly TValue GetValueRefOrNullRefCore(string key) => ref base.GetValueRefOrNullRefCore(key); + + private protected override bool Equals(string? x, string? y) => StringComparer.OrdinalIgnoreCase.Equals(x, y); + private protected override int GetHashCode(string s) => s[HashIndex]; + } +} diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_LeftJustifiedSubstringCaseInsensitive.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_LeftJustifiedSubstringCaseInsensitive.cs new file mode 100644 index 00000000000000..0aec2fbe80473c --- /dev/null +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_LeftJustifiedSubstringCaseInsensitive.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Collections.Frozen +{ + internal sealed class OrdinalStringFrozenDictionary_LeftJustifiedSubstringCaseInsensitive : OrdinalStringFrozenDictionary + { + internal OrdinalStringFrozenDictionary_LeftJustifiedSubstringCaseInsensitive( + string[] keys, + TValue[] values, + IEqualityComparer comparer, + int minimumLength, + int maximumLengthDiff, + int hashIndex, + int hashCount) + : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount) + { + } + + // This override is necessary to force the jit to emit the code in such a way that it + // avoids virtual dispatch overhead when calling the Equals/GetHashCode methods. Don't + // remove this, or you'll tank performance. + private protected override ref readonly TValue GetValueRefOrNullRefCore(string key) => ref base.GetValueRefOrNullRefCore(key); + + private protected override bool Equals(string? x, string? y) => StringComparer.OrdinalIgnoreCase.Equals(x, y); + private protected override int GetHashCode(string s) => Hashing.GetHashCodeOrdinal(s.AsSpan(HashIndex, HashCount)); + } +} diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_RightJustifiedSingleCharCaseInsensitive.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_RightJustifiedSingleCharCaseInsensitive.cs new file mode 100644 index 00000000000000..b60fb38ae32405 --- /dev/null +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_RightJustifiedSingleCharCaseInsensitive.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Collections.Frozen +{ + internal sealed class OrdinalStringFrozenDictionary_RightJustifiedSingleCharCaseInsensitive : OrdinalStringFrozenDictionary + { + internal OrdinalStringFrozenDictionary_RightJustifiedSingleCharCaseInsensitive( + string[] keys, + TValue[] values, + IEqualityComparer comparer, + int minimumLength, + int maximumLengthDiff, + int hashIndex) + : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, 1) + { + } + + // This override is necessary to force the jit to emit the code in such a way that it + // avoids virtual dispatch overhead when calling the Equals/GetHashCode methods. Don't + // remove this, or you'll tank performance. + private protected override ref readonly TValue GetValueRefOrNullRefCore(string key) => ref base.GetValueRefOrNullRefCore(key); + + private protected override bool Equals(string? x, string? y) => StringComparer.OrdinalIgnoreCase.Equals(x, y); + private protected override int GetHashCode(string s) => s[s.Length + HashIndex]; + } +} diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_RightJustifiedSubstringCaseInsensitive.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_RightJustifiedSubstringCaseInsensitive.cs new file mode 100644 index 00000000000000..117d2f329ccf4b --- /dev/null +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_RightJustifiedSubstringCaseInsensitive.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Collections.Frozen +{ + internal sealed class OrdinalStringFrozenDictionary_RightJustifiedSubstringCaseInsensitive : OrdinalStringFrozenDictionary + { + internal OrdinalStringFrozenDictionary_RightJustifiedSubstringCaseInsensitive( + string[] keys, + TValue[] values, + IEqualityComparer comparer, + int minimumLength, + int maximumLengthDiff, + int hashIndex, + int hashCount) + : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount) + { + } + + // This override is necessary to force the jit to emit the code in such a way that it + // avoids virtual dispatch overhead when calling the Equals/GetHashCode methods. Don't + // remove this, or you'll tank performance. + private protected override ref readonly TValue GetValueRefOrNullRefCore(string key) => ref base.GetValueRefOrNullRefCore(key); + + private protected override bool Equals(string? x, string? y) => StringComparer.OrdinalIgnoreCase.Equals(x, y); + private protected override int GetHashCode(string s) => Hashing.GetHashCodeOrdinal(s.AsSpan(s.Length + HashIndex, HashCount)); + } +} diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_FullCaseSensitiveHashCaseInsensitiveEquals.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_FullCaseSensitiveHashCaseInsensitiveEquals.cs new file mode 100644 index 00000000000000..ebcd7b14c6b174 --- /dev/null +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_FullCaseSensitiveHashCaseInsensitiveEquals.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Collections.Frozen +{ + internal sealed class OrdinalStringFrozenSet_FullCaseSensitiveHashCaseInsensitiveEquals : OrdinalStringFrozenSet + { + internal OrdinalStringFrozenSet_FullCaseSensitiveHashCaseInsensitiveEquals( + string[] entries, + IEqualityComparer comparer, + int minimumLength, + int maximumLengthDiff) + : base(entries, comparer, minimumLength, maximumLengthDiff) + { + } + + // This override is necessary to force the jit to emit the code in such a way that it + // avoids virtual dispatch overhead when calling the Equals/GetHashCode methods. Don't + // remove this, or you'll tank performance. + private protected override int FindItemIndex(string item) => base.FindItemIndex(item); + + private protected override bool Equals(string? x, string? y) => StringComparer.OrdinalIgnoreCase.Equals(x, y); + private protected override int GetHashCode(string s) => Hashing.GetHashCodeOrdinal(s.AsSpan()); + } +} diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_LeftJustifiedSingleCharCaseInsensitive.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_LeftJustifiedSingleCharCaseInsensitive.cs new file mode 100644 index 00000000000000..09691e80e75ceb --- /dev/null +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_LeftJustifiedSingleCharCaseInsensitive.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Collections.Frozen +{ + internal sealed class OrdinalStringFrozenSet_LeftJustifiedSingleCharCaseInsensitive : OrdinalStringFrozenSet + { + internal OrdinalStringFrozenSet_LeftJustifiedSingleCharCaseInsensitive( + string[] entries, + IEqualityComparer comparer, + int minimumLength, + int maximumLengthDiff, + int hashIndex) + : base(entries, comparer, minimumLength, maximumLengthDiff, hashIndex, 1) + { + } + + // This override is necessary to force the jit to emit the code in such a way that it + // avoids virtual dispatch overhead when calling the Equals/GetHashCode methods. Don't + // remove this, or you'll tank performance. + private protected override int FindItemIndex(string item) => base.FindItemIndex(item); + + private protected override bool Equals(string? x, string? y) => StringComparer.OrdinalIgnoreCase.Equals(x, y); + private protected override int GetHashCode(string s) => s[HashIndex]; + } +} diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_LeftJustifiedSubstringCaseInsensitive.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_LeftJustifiedSubstringCaseInsensitive.cs new file mode 100644 index 00000000000000..ebcf89c332b9ab --- /dev/null +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_LeftJustifiedSubstringCaseInsensitive.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Collections.Frozen +{ + internal sealed class OrdinalStringFrozenSet_LeftJustifiedSubstringCaseInsensitive : OrdinalStringFrozenSet + { + internal OrdinalStringFrozenSet_LeftJustifiedSubstringCaseInsensitive( + string[] entries, + IEqualityComparer comparer, + int minimumLength, + int maximumLengthDiff, + int hashIndex, + int hashCount) + : base(entries, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount) + { + } + + // This override is necessary to force the jit to emit the code in such a way that it + // avoids virtual dispatch overhead when calling the Equals/GetHashCode methods. Don't + // remove this, or you'll tank performance. + private protected override int FindItemIndex(string item) => base.FindItemIndex(item); + + private protected override bool Equals(string? x, string? y) => StringComparer.OrdinalIgnoreCase.Equals(x, y); + private protected override int GetHashCode(string s) => Hashing.GetHashCodeOrdinal(s.AsSpan(HashIndex, HashCount)); + } +} diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_RightJustifiedSingleCharCaseInsensitive.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_RightJustifiedSingleCharCaseInsensitive.cs new file mode 100644 index 00000000000000..e6843b135f2401 --- /dev/null +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_RightJustifiedSingleCharCaseInsensitive.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Collections.Frozen +{ + internal sealed class OrdinalStringFrozenSet_RightJustifiedSingleCharCaseInsensitive : OrdinalStringFrozenSet + { + internal OrdinalStringFrozenSet_RightJustifiedSingleCharCaseInsensitive( + string[] entries, + IEqualityComparer comparer, + int minimumLength, + int maximumLengthDiff, + int hashIndex) + : base(entries, comparer, minimumLength, maximumLengthDiff, hashIndex, 1) + { + } + + // This override is necessary to force the jit to emit the code in such a way that it + // avoids virtual dispatch overhead when calling the Equals/GetHashCode methods. Don't + // remove this, or you'll tank performance. + private protected override int FindItemIndex(string item) => base.FindItemIndex(item); + + private protected override bool Equals(string? x, string? y) => StringComparer.OrdinalIgnoreCase.Equals(x, y); + private protected override int GetHashCode(string s) => s[s.Length + HashIndex]; + } +} diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_RightJustifiedSubstringCaseInsensitive.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_RightJustifiedSubstringCaseInsensitive.cs new file mode 100644 index 00000000000000..0921da6db088ca --- /dev/null +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_RightJustifiedSubstringCaseInsensitive.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Collections.Frozen +{ + internal sealed class OrdinalStringFrozenSet_RightJustifiedSubstringCaseInsensitive : OrdinalStringFrozenSet + { + internal OrdinalStringFrozenSet_RightJustifiedSubstringCaseInsensitive( + string[] entries, + IEqualityComparer comparer, + int minimumLength, + int maximumLengthDiff, + int hashIndex, + int hashCount) + : base(entries, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount) + { + } + + // This override is necessary to force the jit to emit the code in such a way that it + // avoids virtual dispatch overhead when calling the Equals/GetHashCode methods. Don't + // remove this, or you'll tank performance. + private protected override int FindItemIndex(string item) => base.FindItemIndex(item); + + private protected override bool Equals(string? x, string? y) => StringComparer.OrdinalIgnoreCase.Equals(x, y); + private protected override int GetHashCode(string s) => Hashing.GetHashCodeOrdinal(s.AsSpan(s.Length + HashIndex, HashCount)); + } +} diff --git a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs index 179506b896bdd7..1b68dfbdfb4c7e 100644 --- a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs +++ b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenFromKnownValuesTests.cs @@ -197,8 +197,8 @@ private static void FrozenDictionaryWorker(Dictionary pair in source) { - var keyUpper = (TKey)(object)((string)(object)pair.Key).ToUpper(); - var isValidTest = frozen.Comparer.Equals(pair.Key, keyUpper); + TKey keyUpper = (TKey)(object)((string)(object)pair.Key).ToUpper(); + bool isValidTest = frozen.Comparer.Equals(pair.Key, keyUpper); if (isValidTest) { Assert.Equal(pair.Value, frozen.GetValueRefOrNullRef(keyUpper)); @@ -293,8 +293,8 @@ private void FrozenSetWorker(Dictionary source) { foreach (KeyValuePair pair in source) { - var keyUpper = (TKey)(object)((string)(object)pair.Key).ToUpper(); - var isValidTest = frozen.Comparer.Equals(pair.Key, keyUpper); + TKey keyUpper = (TKey)(object)((string)(object)pair.Key).ToUpper(); + bool isValidTest = frozen.Comparer.Equals(pair.Key, keyUpper); if (isValidTest) { Assert.True(frozen.Contains(keyUpper)); diff --git a/src/libraries/System.Collections.Immutable/tests/Frozen/KeyAnalyzerTests.cs b/src/libraries/System.Collections.Immutable/tests/Frozen/KeyAnalyzerTests.cs index da729d7abe8ba1..6ce285131b17ee 100644 --- a/src/libraries/System.Collections.Immutable/tests/Frozen/KeyAnalyzerTests.cs +++ b/src/libraries/System.Collections.Immutable/tests/Frozen/KeyAnalyzerTests.cs @@ -32,25 +32,29 @@ public static void LeftHand() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "K0", "K20", "K300" }, false); Assert.False(r.RightJustifiedSubstring); - Assert.False(r.IgnoreCase); + Assert.False(r.IgnoreCaseForHash); + Assert.False(r.IgnoreCaseForEquals); Assert.Equal(1, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "S1" }, false); Assert.False(r.RightJustifiedSubstring); - Assert.False(r.IgnoreCase); + Assert.False(r.IgnoreCaseForHash); + Assert.False(r.IgnoreCaseForEquals); Assert.Equal(0, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "S1", "T1" }, false); Assert.False(r.RightJustifiedSubstring); - Assert.False(r.IgnoreCase); + Assert.False(r.IgnoreCaseForHash); + Assert.False(r.IgnoreCaseForEquals); Assert.Equal(0, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "SA1", "TA1", "SB1" }, false); Assert.False(r.RightJustifiedSubstring); - Assert.False(r.IgnoreCase); + Assert.False(r.IgnoreCaseForHash); + Assert.False(r.IgnoreCaseForEquals); Assert.Equal(0, r.HashIndex); Assert.Equal(2, r.HashCount); } @@ -60,45 +64,60 @@ public static void LeftHandCaseInsensitive() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "É1" }, true); Assert.False(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCase); + Assert.True(r.IgnoreCaseForHash); + Assert.True(r.IgnoreCaseForEquals); Assert.False(r.AllAsciiIfIgnoreCase); Assert.Equal(0, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "É1", "T1" }, true); Assert.False(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCase); + Assert.True(r.IgnoreCaseForHash); + Assert.True(r.IgnoreCaseForEquals); Assert.False(r.AllAsciiIfIgnoreCase); Assert.Equal(0, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "ÉA1", "TA1", "ÉB1" }, true); Assert.False(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCase); + Assert.True(r.IgnoreCaseForHash); + Assert.True(r.IgnoreCaseForEquals); Assert.False(r.AllAsciiIfIgnoreCase); Assert.Equal(0, r.HashIndex); Assert.Equal(2, r.HashCount); r = RunAnalysis(new[] { "ABCDEÉ1ABCDEF", "ABCDETA1ABCDEF", "ABCDESB1ABCDEF" }, true); Assert.False(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCase); + Assert.True(r.IgnoreCaseForHash); + Assert.True(r.IgnoreCaseForEquals); Assert.False(r.AllAsciiIfIgnoreCase); Assert.Equal(5, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "ABCDEFÉ1ABCDEF", "ABCDEFTA1ABCDEF", "ABCDEFSB1ABCDEF" }, true); Assert.False(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCase); + Assert.True(r.IgnoreCaseForHash); + Assert.True(r.IgnoreCaseForEquals); Assert.False(r.AllAsciiIfIgnoreCase); Assert.Equal(6, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "ABCÉDEFÉ1ABCDEF", "ABCÉDEFTA1ABCDEF", "ABCÉDEFSB1ABCDEF" }, true); Assert.False(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCase); + Assert.True(r.IgnoreCaseForHash); + Assert.True(r.IgnoreCaseForEquals); Assert.False(r.AllAsciiIfIgnoreCase); Assert.Equal(7, r.HashIndex); Assert.Equal(1, r.HashCount); + + r = RunAnalysis(new[] { "1abc", "2abc", "3abc", "4abc", "5abc", "6abc" }, true); + Assert.False(r.RightJustifiedSubstring); + Assert.False(r.IgnoreCaseForHash); + Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.AllAsciiIfIgnoreCase); + Assert.Equal(0, r.HashIndex); + Assert.Equal(1, r.HashCount); + } [Fact] @@ -106,21 +125,24 @@ public static void LeftHandCaseInsensitiveAscii() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "S1" }, true); Assert.False(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCase); + Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCaseForHash); Assert.True(r.AllAsciiIfIgnoreCase); Assert.Equal(0, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "S1", "T1" }, true); Assert.False(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCase); + Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCaseForHash); Assert.True(r.AllAsciiIfIgnoreCase); Assert.Equal(0, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "SA1", "TA1", "SB1" }, true); Assert.False(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCase); + Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCaseForHash); Assert.True(r.AllAsciiIfIgnoreCase); Assert.Equal(0, r.HashIndex); Assert.Equal(2, r.HashCount); @@ -131,14 +153,16 @@ public static void RightHand() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "1T1", "1T" }, false); Assert.True(r.RightJustifiedSubstring); - Assert.False(r.IgnoreCase); + Assert.False(r.IgnoreCaseForEquals); + Assert.False(r.IgnoreCaseForHash); Assert.True(r.AllAsciiIfIgnoreCase); Assert.Equal(-1, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "1ATA", "1ATB", "1BS" }, false); Assert.True(r.RightJustifiedSubstring); - Assert.False(r.IgnoreCase); + Assert.False(r.IgnoreCaseForEquals); + Assert.False(r.IgnoreCaseForHash); Assert.True(r.AllAsciiIfIgnoreCase); Assert.Equal(-1, r.HashIndex); Assert.Equal(1, r.HashCount); @@ -149,14 +173,16 @@ public static void RightHandCaseInsensitive() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "1ÉÉ", "1É" }, true); Assert.True(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCase); + Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCaseForHash); Assert.False(r.AllAsciiIfIgnoreCase); Assert.Equal(-2, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "ÉA", "1AT", "1AÉT" }, true); Assert.True(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCase); + Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCaseForHash); Assert.False(r.AllAsciiIfIgnoreCase); Assert.Equal(-2, r.HashIndex); Assert.Equal(2, r.HashCount); @@ -167,14 +193,16 @@ public static void RightHandCaseInsensitiveAscii() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "a1", "A1T" }, true); Assert.True(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCase); + Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCaseForHash); Assert.True(r.AllAsciiIfIgnoreCase); Assert.Equal(-1, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "bÉÉ", "caT", "cAÉT" }, true); Assert.True(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCase); + Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCaseForHash); Assert.True(r.AllAsciiIfIgnoreCase); Assert.Equal(-3, r.HashIndex); Assert.Equal(1, r.HashCount); @@ -185,7 +213,8 @@ public static void Full() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "ABC", "DBC", "ADC", "ABD", "ABDABD" }, false); Assert.False(r.SubstringHashing); - Assert.False(r.IgnoreCase); + Assert.False(r.IgnoreCaseForEquals); + Assert.False(r.IgnoreCaseForHash); Assert.True(r.AllAsciiIfIgnoreCase); } @@ -194,7 +223,8 @@ public static void FullCaseInsensitive() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "æbc", "DBC", "æDC", "æbd", "æbdæbd" }, true); Assert.False(r.SubstringHashing); - Assert.True(r.IgnoreCase); + Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCaseForHash); Assert.False(r.AllAsciiIfIgnoreCase); } @@ -203,7 +233,8 @@ public static void FullCaseInsensitiveAscii() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "abc", "DBC", "aDC", "abd", "abdabd" }, true); Assert.False(r.SubstringHashing); - Assert.True(r.IgnoreCase); + Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCaseForHash); Assert.True(r.AllAsciiIfIgnoreCase); } From 328d9f13c03888dc8f359c34dd9871b023769c57 Mon Sep 17 00:00:00 2001 From: Andrew J Said Date: Thu, 26 Oct 2023 23:11:18 +0100 Subject: [PATCH 3/4] When hashing the entire string, case sensitivity of hash and equals should be the same --- .../src/System.Collections.Immutable.csproj | 3 -- .../Collections/Frozen/FrozenDictionary.cs | 5 ++-- .../System/Collections/Frozen/FrozenSet.cs | 5 ++-- ...lCaseSensitiveHashCaseInsensitiveEquals.cs | 28 ------------------- ...lCaseSensitiveHashCaseInsensitiveEquals.cs | 27 ------------------ 5 files changed, 4 insertions(+), 64 deletions(-) delete mode 100644 src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_FullCaseSensitiveHashCaseInsensitiveEquals.cs delete mode 100644 src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_FullCaseSensitiveHashCaseInsensitiveEquals.cs diff --git a/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj b/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj index 33eb902cddff64..273897b01524af 100644 --- a/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj +++ b/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj @@ -66,13 +66,10 @@ The System.Collections.Immutable library is built-in as part of the shared frame - - - diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.cs index b0fad1cd1115c3..3dddb87219090e 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.cs @@ -238,9 +238,8 @@ private static FrozenDictionary CreateFromDictionary } else { - frozenDictionary = analysis.IgnoreCaseForEquals - ? new OrdinalStringFrozenDictionary_FullCaseSensitiveHashCaseInsensitiveEquals(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff) - : new OrdinalStringFrozenDictionary_Full(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff); + // if (IgnoreCaseForEquals) => Can only be true if there are no letters, thus case sensitive comparison still works here. + frozenDictionary = new OrdinalStringFrozenDictionary_Full(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff); } } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs index 63c2be5dbec1ba..608668351188c5 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs @@ -186,9 +186,8 @@ private static FrozenSet CreateFromSet(HashSet source) } else { - frozenSet = analysis.IgnoreCaseForEquals - ? new OrdinalStringFrozenSet_FullCaseSensitiveHashCaseInsensitiveEquals(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff) - : new OrdinalStringFrozenSet_Full(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff); + // if (IgnoreCaseForEquals) => Can only be true if there are no letters, thus case sensitive comparison still works here. + frozenSet = new OrdinalStringFrozenSet_Full(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff); } } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_FullCaseSensitiveHashCaseInsensitiveEquals.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_FullCaseSensitiveHashCaseInsensitiveEquals.cs deleted file mode 100644 index f3c805e416054c..00000000000000 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenDictionary_FullCaseSensitiveHashCaseInsensitiveEquals.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; - -namespace System.Collections.Frozen -{ - internal sealed class OrdinalStringFrozenDictionary_FullCaseSensitiveHashCaseInsensitiveEquals : OrdinalStringFrozenDictionary - { - internal OrdinalStringFrozenDictionary_FullCaseSensitiveHashCaseInsensitiveEquals( - string[] keys, - TValue[] values, - IEqualityComparer comparer, - int minimumLength, - int maximumLengthDiff) - : base(keys, values, comparer, minimumLength, maximumLengthDiff) - { - } - - // This override is necessary to force the jit to emit the code in such a way that it - // avoids virtual dispatch overhead when calling the Equals/GetHashCode methods. Don't - // remove this, or you'll tank performance. - private protected override ref readonly TValue GetValueRefOrNullRefCore(string key) => ref base.GetValueRefOrNullRefCore(key); - - private protected override bool Equals(string? x, string? y) => StringComparer.OrdinalIgnoreCase.Equals(x, y); - private protected override int GetHashCode(string s) => Hashing.GetHashCodeOrdinal(s.AsSpan()); - } -} diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_FullCaseSensitiveHashCaseInsensitiveEquals.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_FullCaseSensitiveHashCaseInsensitiveEquals.cs deleted file mode 100644 index ebcd7b14c6b174..00000000000000 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/OrdinalStringFrozenSet_FullCaseSensitiveHashCaseInsensitiveEquals.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; - -namespace System.Collections.Frozen -{ - internal sealed class OrdinalStringFrozenSet_FullCaseSensitiveHashCaseInsensitiveEquals : OrdinalStringFrozenSet - { - internal OrdinalStringFrozenSet_FullCaseSensitiveHashCaseInsensitiveEquals( - string[] entries, - IEqualityComparer comparer, - int minimumLength, - int maximumLengthDiff) - : base(entries, comparer, minimumLength, maximumLengthDiff) - { - } - - // This override is necessary to force the jit to emit the code in such a way that it - // avoids virtual dispatch overhead when calling the Equals/GetHashCode methods. Don't - // remove this, or you'll tank performance. - private protected override int FindItemIndex(string item) => base.FindItemIndex(item); - - private protected override bool Equals(string? x, string? y) => StringComparer.OrdinalIgnoreCase.Equals(x, y); - private protected override int GetHashCode(string s) => Hashing.GetHashCodeOrdinal(s.AsSpan()); - } -} From 2f2a018de3082152e08e4b6582db6d17ef99036d Mon Sep 17 00:00:00 2001 From: Andrew J Said Date: Wed, 8 Nov 2023 22:00:40 +0000 Subject: [PATCH 4/4] Address code review comments --- .../Collections/Frozen/FrozenDictionary.cs | 19 +++-- .../System/Collections/Frozen/FrozenSet.cs | 19 +++-- .../Collections/Frozen/String/KeyAnalyzer.cs | 32 +++---- .../tests/Frozen/KeyAnalyzerTests.cs | 84 +++++++++---------- 4 files changed, 80 insertions(+), 74 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.cs index 3dddb87219090e..cfde792bfc71f2 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.cs @@ -183,7 +183,8 @@ private static FrozenDictionary CreateFromDictionary { if (analysis.IgnoreCaseForHash) { - frozenDictionary = analysis.AllAsciiIfIgnoreCase + Debug.Assert(analysis.IgnoreCase); + frozenDictionary = analysis.AllAsciiIfIgnoreCaseForHash ? new OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveAsciiSubstring(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) : new OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveSubstring(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); } @@ -191,13 +192,13 @@ private static FrozenDictionary CreateFromDictionary { if (analysis.HashCount == 1) { - frozenDictionary = analysis.IgnoreCaseForEquals + frozenDictionary = analysis.IgnoreCase ? new OrdinalStringFrozenDictionary_RightJustifiedSingleCharCaseInsensitive(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex) : new OrdinalStringFrozenDictionary_RightJustifiedSingleChar(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex); } else { - frozenDictionary = analysis.IgnoreCaseForEquals + frozenDictionary = analysis.IgnoreCase ? new OrdinalStringFrozenDictionary_RightJustifiedSubstringCaseInsensitive(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) : new OrdinalStringFrozenDictionary_RightJustifiedSubstring(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); } @@ -207,7 +208,8 @@ private static FrozenDictionary CreateFromDictionary { if (analysis.IgnoreCaseForHash) { - frozenDictionary = analysis.AllAsciiIfIgnoreCase + Debug.Assert(analysis.IgnoreCase); + frozenDictionary = analysis.AllAsciiIfIgnoreCaseForHash ? new OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveAsciiSubstring(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) : new OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveSubstring(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); } @@ -215,13 +217,13 @@ private static FrozenDictionary CreateFromDictionary { if (analysis.HashCount == 1) { - frozenDictionary = analysis.IgnoreCaseForEquals + frozenDictionary = analysis.IgnoreCase ? new OrdinalStringFrozenDictionary_LeftJustifiedSingleCharCaseInsensitive(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex) : new OrdinalStringFrozenDictionary_LeftJustifiedSingleChar(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex); } else { - frozenDictionary = analysis.IgnoreCaseForEquals + frozenDictionary = analysis.IgnoreCase ? new OrdinalStringFrozenDictionary_LeftJustifiedSubstringCaseInsensitive(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) : new OrdinalStringFrozenDictionary_LeftJustifiedSubstring(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); } @@ -232,13 +234,14 @@ private static FrozenDictionary CreateFromDictionary { if (analysis.IgnoreCaseForHash) { - frozenDictionary = analysis.AllAsciiIfIgnoreCase + Debug.Assert(analysis.IgnoreCase); + frozenDictionary = analysis.AllAsciiIfIgnoreCaseForHash ? new OrdinalStringFrozenDictionary_FullCaseInsensitiveAscii(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff) : new OrdinalStringFrozenDictionary_FullCaseInsensitive(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff); } else { - // if (IgnoreCaseForEquals) => Can only be true if there are no letters, thus case sensitive comparison still works here. + // if (IgnoreCase) => Can only be true if there are no letters, thus case sensitive comparison still works here. frozenDictionary = new OrdinalStringFrozenDictionary_Full(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff); } } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs index 608668351188c5..4bafeccd3882a2 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs @@ -131,7 +131,8 @@ private static FrozenSet CreateFromSet(HashSet source) { if (analysis.IgnoreCaseForHash) { - frozenSet = analysis.AllAsciiIfIgnoreCase + Debug.Assert(analysis.IgnoreCase); + frozenSet = analysis.AllAsciiIfIgnoreCaseForHash ? new OrdinalStringFrozenSet_RightJustifiedCaseInsensitiveAsciiSubstring(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) : new OrdinalStringFrozenSet_RightJustifiedCaseInsensitiveSubstring(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); } @@ -139,13 +140,13 @@ private static FrozenSet CreateFromSet(HashSet source) { if (analysis.HashCount == 1) { - frozenSet = analysis.IgnoreCaseForEquals + frozenSet = analysis.IgnoreCase ? new OrdinalStringFrozenSet_RightJustifiedSingleCharCaseInsensitive(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex) : new OrdinalStringFrozenSet_RightJustifiedSingleChar(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex); ; } else { - frozenSet = analysis.IgnoreCaseForEquals + frozenSet = analysis.IgnoreCase ? new OrdinalStringFrozenSet_RightJustifiedSubstringCaseInsensitive(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) : new OrdinalStringFrozenSet_RightJustifiedSubstring(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); } @@ -155,7 +156,8 @@ private static FrozenSet CreateFromSet(HashSet source) { if (analysis.IgnoreCaseForHash) { - frozenSet = analysis.AllAsciiIfIgnoreCase + Debug.Assert(analysis.IgnoreCase); + frozenSet = analysis.AllAsciiIfIgnoreCaseForHash ? new OrdinalStringFrozenSet_LeftJustifiedCaseInsensitiveAsciiSubstring(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) : new OrdinalStringFrozenSet_LeftJustifiedCaseInsensitiveSubstring(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); } @@ -163,13 +165,13 @@ private static FrozenSet CreateFromSet(HashSet source) { if (analysis.HashCount == 1) { - frozenSet = analysis.IgnoreCaseForEquals + frozenSet = analysis.IgnoreCase ? new OrdinalStringFrozenSet_LeftJustifiedSingleCharCaseInsensitive(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex) : new OrdinalStringFrozenSet_LeftJustifiedSingleChar(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex); } else { - frozenSet = analysis.IgnoreCaseForEquals + frozenSet = analysis.IgnoreCase ? new OrdinalStringFrozenSet_LeftJustifiedSubstringCaseInsensitive(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount) : new OrdinalStringFrozenSet_LeftJustifiedSubstring(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount); } @@ -180,13 +182,14 @@ private static FrozenSet CreateFromSet(HashSet source) { if (analysis.IgnoreCaseForHash) { - frozenSet = analysis.AllAsciiIfIgnoreCase + Debug.Assert(analysis.IgnoreCase); + frozenSet = analysis.AllAsciiIfIgnoreCaseForHash ? new OrdinalStringFrozenSet_FullCaseInsensitiveAscii(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff) : new OrdinalStringFrozenSet_FullCaseInsensitive(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff); } else { - // if (IgnoreCaseForEquals) => Can only be true if there are no letters, thus case sensitive comparison still works here. + // if (IgnoreCase) => Can only be true if there are no letters, thus case sensitive comparison still works here. frozenSet = new OrdinalStringFrozenSet_Full(entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff); } } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/KeyAnalyzer.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/KeyAnalyzer.cs index 9fb6f4ad4258e7..41931cbc55981b 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/KeyAnalyzer.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/KeyAnalyzer.cs @@ -118,20 +118,20 @@ private static AnalysisResults CreateAnalysisResults( ReadOnlySpan uniqueStrings, bool ignoreCase, int minLength, int maxLength, int index, int count, GetSpan getSubstringSpan) { // Start off by assuming all strings are ASCII - bool allAsciiIfIgnoreCase = true; + bool allAsciiIfIgnoreCaseForHash = true; - bool ignoreCaseForEquals = ignoreCase; + bool ignoreCaseForHash = ignoreCase; // If we're case-sensitive, it doesn't matter if the strings are ASCII or not. // But if we're case-insensitive, we can switch to a faster comparer if all the // substrings are ASCII, so we check each. - if (ignoreCase) + if (ignoreCaseForHash) { // Further, if the ASCII substrings don't contain any letters, then we can // actually perform the comparison as case-sensitive even if case-insensitive // was requested, as there's nothing that would compare equally to the substring // other than the substring itself. - bool canSwitchIgnoreCaseToCaseSensitive = true; + bool canSwitchIgnoreCaseHashToCaseSensitive = true; foreach (string s in uniqueStrings) { @@ -141,28 +141,28 @@ private static AnalysisResults CreateAnalysisResults( // If the substring isn't ASCII, bail out to return the results. if (!IsAllAscii(substring)) { - allAsciiIfIgnoreCase = false; - canSwitchIgnoreCaseToCaseSensitive = false; + allAsciiIfIgnoreCaseForHash = false; + canSwitchIgnoreCaseHashToCaseSensitive = false; break; } // All substrings so far are still ASCII only. If this one contains any ASCII // letters, mark that we can't switch to case-sensitive. - if (canSwitchIgnoreCaseToCaseSensitive && ContainsAnyLetters(substring)) + if (canSwitchIgnoreCaseHashToCaseSensitive && ContainsAnyLetters(substring)) { - canSwitchIgnoreCaseToCaseSensitive = false; + canSwitchIgnoreCaseHashToCaseSensitive = false; } } // If we can switch to case-sensitive, do so. - if (canSwitchIgnoreCaseToCaseSensitive) + if (canSwitchIgnoreCaseHashToCaseSensitive) { - ignoreCase = false; + ignoreCaseForHash = false; } } // Return the analysis results. - return new AnalysisResults(ignoreCase, ignoreCaseForEquals, allAsciiIfIgnoreCase, index, count, minLength, maxLength); + return new AnalysisResults(ignoreCase, ignoreCaseForHash, allAsciiIfIgnoreCaseForHash, index, count, minLength, maxLength); } private delegate ReadOnlySpan GetSpan(string s, int index, int count); @@ -245,20 +245,20 @@ internal static bool HasSufficientUniquenessFactor(HashSet set, ReadOnly internal readonly struct AnalysisResults { - public AnalysisResults(bool ignoreCaseForHash, bool ignoreCaseForEquals, bool allAsciiIfIgnoreCase, int hashIndex, int hashCount, int minLength, int maxLength) + public AnalysisResults(bool ignoreCase, bool ignoreCaseForHash, bool allAsciiIfIgnoreCaseForHash, int hashIndex, int hashCount, int minLength, int maxLength) { + IgnoreCase = ignoreCase; IgnoreCaseForHash = ignoreCaseForHash; - IgnoreCaseForEquals = ignoreCaseForEquals; - AllAsciiIfIgnoreCase = allAsciiIfIgnoreCase; + AllAsciiIfIgnoreCaseForHash = allAsciiIfIgnoreCaseForHash; HashIndex = hashIndex; HashCount = hashCount; MinimumLength = minLength; MaximumLengthDiff = maxLength - minLength; } + public bool IgnoreCase { get; } public bool IgnoreCaseForHash { get; } - public bool IgnoreCaseForEquals { get; } - public bool AllAsciiIfIgnoreCase { get; } + public bool AllAsciiIfIgnoreCaseForHash { get; } public int HashIndex { get; } public int HashCount { get; } public int MinimumLength { get; } diff --git a/src/libraries/System.Collections.Immutable/tests/Frozen/KeyAnalyzerTests.cs b/src/libraries/System.Collections.Immutable/tests/Frozen/KeyAnalyzerTests.cs index 6ce285131b17ee..141e07162d572f 100644 --- a/src/libraries/System.Collections.Immutable/tests/Frozen/KeyAnalyzerTests.cs +++ b/src/libraries/System.Collections.Immutable/tests/Frozen/KeyAnalyzerTests.cs @@ -33,28 +33,28 @@ public static void LeftHand() KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "K0", "K20", "K300" }, false); Assert.False(r.RightJustifiedSubstring); Assert.False(r.IgnoreCaseForHash); - Assert.False(r.IgnoreCaseForEquals); + Assert.False(r.IgnoreCase); Assert.Equal(1, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "S1" }, false); Assert.False(r.RightJustifiedSubstring); Assert.False(r.IgnoreCaseForHash); - Assert.False(r.IgnoreCaseForEquals); + Assert.False(r.IgnoreCase); Assert.Equal(0, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "S1", "T1" }, false); Assert.False(r.RightJustifiedSubstring); Assert.False(r.IgnoreCaseForHash); - Assert.False(r.IgnoreCaseForEquals); + Assert.False(r.IgnoreCase); Assert.Equal(0, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "SA1", "TA1", "SB1" }, false); Assert.False(r.RightJustifiedSubstring); Assert.False(r.IgnoreCaseForHash); - Assert.False(r.IgnoreCaseForEquals); + Assert.False(r.IgnoreCase); Assert.Equal(0, r.HashIndex); Assert.Equal(2, r.HashCount); } @@ -65,56 +65,56 @@ public static void LeftHandCaseInsensitive() KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "É1" }, true); Assert.False(r.RightJustifiedSubstring); Assert.True(r.IgnoreCaseForHash); - Assert.True(r.IgnoreCaseForEquals); - Assert.False(r.AllAsciiIfIgnoreCase); + Assert.True(r.IgnoreCase); + Assert.False(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(0, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "É1", "T1" }, true); Assert.False(r.RightJustifiedSubstring); Assert.True(r.IgnoreCaseForHash); - Assert.True(r.IgnoreCaseForEquals); - Assert.False(r.AllAsciiIfIgnoreCase); + Assert.True(r.IgnoreCase); + Assert.False(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(0, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "ÉA1", "TA1", "ÉB1" }, true); Assert.False(r.RightJustifiedSubstring); Assert.True(r.IgnoreCaseForHash); - Assert.True(r.IgnoreCaseForEquals); - Assert.False(r.AllAsciiIfIgnoreCase); + Assert.True(r.IgnoreCase); + Assert.False(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(0, r.HashIndex); Assert.Equal(2, r.HashCount); r = RunAnalysis(new[] { "ABCDEÉ1ABCDEF", "ABCDETA1ABCDEF", "ABCDESB1ABCDEF" }, true); Assert.False(r.RightJustifiedSubstring); Assert.True(r.IgnoreCaseForHash); - Assert.True(r.IgnoreCaseForEquals); - Assert.False(r.AllAsciiIfIgnoreCase); + Assert.True(r.IgnoreCase); + Assert.False(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(5, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "ABCDEFÉ1ABCDEF", "ABCDEFTA1ABCDEF", "ABCDEFSB1ABCDEF" }, true); Assert.False(r.RightJustifiedSubstring); Assert.True(r.IgnoreCaseForHash); - Assert.True(r.IgnoreCaseForEquals); - Assert.False(r.AllAsciiIfIgnoreCase); + Assert.True(r.IgnoreCase); + Assert.False(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(6, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "ABCÉDEFÉ1ABCDEF", "ABCÉDEFTA1ABCDEF", "ABCÉDEFSB1ABCDEF" }, true); Assert.False(r.RightJustifiedSubstring); Assert.True(r.IgnoreCaseForHash); - Assert.True(r.IgnoreCaseForEquals); - Assert.False(r.AllAsciiIfIgnoreCase); + Assert.True(r.IgnoreCase); + Assert.False(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(7, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "1abc", "2abc", "3abc", "4abc", "5abc", "6abc" }, true); Assert.False(r.RightJustifiedSubstring); Assert.False(r.IgnoreCaseForHash); - Assert.True(r.IgnoreCaseForEquals); - Assert.True(r.AllAsciiIfIgnoreCase); + Assert.True(r.IgnoreCase); + Assert.True(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(0, r.HashIndex); Assert.Equal(1, r.HashCount); @@ -125,25 +125,25 @@ public static void LeftHandCaseInsensitiveAscii() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "S1" }, true); Assert.False(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCase); Assert.True(r.IgnoreCaseForHash); - Assert.True(r.AllAsciiIfIgnoreCase); + Assert.True(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(0, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "S1", "T1" }, true); Assert.False(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCase); Assert.True(r.IgnoreCaseForHash); - Assert.True(r.AllAsciiIfIgnoreCase); + Assert.True(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(0, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "SA1", "TA1", "SB1" }, true); Assert.False(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCase); Assert.True(r.IgnoreCaseForHash); - Assert.True(r.AllAsciiIfIgnoreCase); + Assert.True(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(0, r.HashIndex); Assert.Equal(2, r.HashCount); } @@ -153,17 +153,17 @@ public static void RightHand() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "1T1", "1T" }, false); Assert.True(r.RightJustifiedSubstring); - Assert.False(r.IgnoreCaseForEquals); + Assert.False(r.IgnoreCase); Assert.False(r.IgnoreCaseForHash); - Assert.True(r.AllAsciiIfIgnoreCase); + Assert.True(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(-1, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "1ATA", "1ATB", "1BS" }, false); Assert.True(r.RightJustifiedSubstring); - Assert.False(r.IgnoreCaseForEquals); + Assert.False(r.IgnoreCase); Assert.False(r.IgnoreCaseForHash); - Assert.True(r.AllAsciiIfIgnoreCase); + Assert.True(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(-1, r.HashIndex); Assert.Equal(1, r.HashCount); } @@ -173,17 +173,17 @@ public static void RightHandCaseInsensitive() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "1ÉÉ", "1É" }, true); Assert.True(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCase); Assert.True(r.IgnoreCaseForHash); - Assert.False(r.AllAsciiIfIgnoreCase); + Assert.False(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(-2, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "ÉA", "1AT", "1AÉT" }, true); Assert.True(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCase); Assert.True(r.IgnoreCaseForHash); - Assert.False(r.AllAsciiIfIgnoreCase); + Assert.False(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(-2, r.HashIndex); Assert.Equal(2, r.HashCount); } @@ -193,17 +193,17 @@ public static void RightHandCaseInsensitiveAscii() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "a1", "A1T" }, true); Assert.True(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCase); Assert.True(r.IgnoreCaseForHash); - Assert.True(r.AllAsciiIfIgnoreCase); + Assert.True(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(-1, r.HashIndex); Assert.Equal(1, r.HashCount); r = RunAnalysis(new[] { "bÉÉ", "caT", "cAÉT" }, true); Assert.True(r.RightJustifiedSubstring); - Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCase); Assert.True(r.IgnoreCaseForHash); - Assert.True(r.AllAsciiIfIgnoreCase); + Assert.True(r.AllAsciiIfIgnoreCaseForHash); Assert.Equal(-3, r.HashIndex); Assert.Equal(1, r.HashCount); } @@ -213,9 +213,9 @@ public static void Full() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "ABC", "DBC", "ADC", "ABD", "ABDABD" }, false); Assert.False(r.SubstringHashing); - Assert.False(r.IgnoreCaseForEquals); + Assert.False(r.IgnoreCase); Assert.False(r.IgnoreCaseForHash); - Assert.True(r.AllAsciiIfIgnoreCase); + Assert.True(r.AllAsciiIfIgnoreCaseForHash); } [Fact] @@ -223,9 +223,9 @@ public static void FullCaseInsensitive() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "æbc", "DBC", "æDC", "æbd", "æbdæbd" }, true); Assert.False(r.SubstringHashing); - Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCase); Assert.True(r.IgnoreCaseForHash); - Assert.False(r.AllAsciiIfIgnoreCase); + Assert.False(r.AllAsciiIfIgnoreCaseForHash); } [Fact] @@ -233,9 +233,9 @@ public static void FullCaseInsensitiveAscii() { KeyAnalyzer.AnalysisResults r = RunAnalysis(new[] { "abc", "DBC", "aDC", "abd", "abdabd" }, true); Assert.False(r.SubstringHashing); - Assert.True(r.IgnoreCaseForEquals); + Assert.True(r.IgnoreCase); Assert.True(r.IgnoreCaseForHash); - Assert.True(r.AllAsciiIfIgnoreCase); + Assert.True(r.AllAsciiIfIgnoreCaseForHash); } [Fact]