// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable enable using System; using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis; namespace SharpGenTools.Sdk.Internal.Roslyn { internal static class Hash { /// /// This is how VB Anonymous Types combine hash values for fields. /// internal static int Combine(int newKey, int currentKey) { return unchecked((currentKey * (int)0xA5555529) + newKey); } internal static int Combine(bool newKeyPart, int currentKey) { return Combine(currentKey, newKeyPart ? 1 : 0); } /// /// This is how VB Anonymous Types combine hash values for fields. /// PERF: Do not use with enum types because that involves multiple /// unnecessary boxing operations. Unfortunately, we can't constrain /// T to "non-enum", so we'll use a more restrictive constraint. /// internal static int Combine(T newKeyPart, int currentKey) where T : class? { int hash = unchecked(currentKey * (int)0xA5555529); if (newKeyPart != null) { return unchecked(hash + newKeyPart.GetHashCode()); } return hash; } internal static int CombineValues(IEnumerable? values, int maxItemsToHash = int.MaxValue) { if (values == null) { return 0; } var hashCode = 0; var count = 0; foreach (var value in values) { if (count++ >= maxItemsToHash) { break; } // Should end up with a constrained virtual call to object.GetHashCode (i.e. avoid boxing where possible). if (value != null) { hashCode = Hash.Combine(value.GetHashCode(), hashCode); } } return hashCode; } internal static int CombineValues(T[]? values, int maxItemsToHash = int.MaxValue) { if (values == null) { return 0; } var maxSize = Math.Min(maxItemsToHash, values.Length); var hashCode = 0; for (int i = 0; i < maxSize; i++) { T value = values[i]; // Should end up with a constrained virtual call to object.GetHashCode (i.e. avoid boxing where possible). if (value != null) { hashCode = Hash.Combine(value.GetHashCode(), hashCode); } } return hashCode; } internal static int CombineValues(ImmutableArray values, int maxItemsToHash = int.MaxValue) { if (values.IsDefaultOrEmpty) { return 0; } var hashCode = 0; var count = 0; foreach (var value in values) { if (count++ >= maxItemsToHash) { break; } // Should end up with a constrained virtual call to object.GetHashCode (i.e. avoid boxing where possible). if (value != null) { hashCode = Hash.Combine(value.GetHashCode(), hashCode); } } return hashCode; } internal static int CombineValues(IEnumerable? values, StringComparer stringComparer, int maxItemsToHash = int.MaxValue) { if (values == null) { return 0; } var hashCode = 0; var count = 0; foreach (var value in values) { if (count++ >= maxItemsToHash) { break; } if (value != null) { hashCode = Hash.Combine(stringComparer.GetHashCode(value), hashCode); } } return hashCode; } /// /// The offset bias value used in the FNV-1a algorithm /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function /// internal const int FnvOffsetBias = unchecked((int)2166136261); /// /// The generative factor used in the FNV-1a algorithm /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function /// internal const int FnvPrime = 16777619; /// /// Compute the FNV-1a hash of a sequence of bytes /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function /// /// The sequence of bytes /// The FNV-1a hash of internal static int GetFNVHashCode(byte[] data) { int hashCode = Hash.FnvOffsetBias; for (int i = 0; i < data.Length; i++) { hashCode = unchecked((hashCode ^ data[i]) * Hash.FnvPrime); } return hashCode; } /// /// Compute the FNV-1a hash of a sequence of bytes and determines if the byte /// sequence is valid ASCII and hence the hash code matches a char sequence /// encoding the same text. /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function /// /// The sequence of bytes that are likely to be ASCII text. /// True if the sequence contains only characters in the ASCII range. /// The FNV-1a hash of internal static int GetFNVHashCode(ReadOnlySpan data, out bool isAscii) { int hashCode = Hash.FnvOffsetBias; byte asciiMask = 0; for (int i = 0; i < data.Length; i++) { byte b = data[i]; asciiMask |= b; hashCode = unchecked((hashCode ^ b) * Hash.FnvPrime); } isAscii = (asciiMask & 0x80) == 0; return hashCode; } /// /// Compute the FNV-1a hash of a sequence of bytes /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function /// /// The sequence of bytes /// The FNV-1a hash of internal static int GetFNVHashCode(ImmutableArray data) { int hashCode = Hash.FnvOffsetBias; for (int i = 0; i < data.Length; i++) { hashCode = unchecked((hashCode ^ data[i]) * Hash.FnvPrime); } return hashCode; } /// /// Compute the hashcode of a sub-string using FNV-1a /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function /// Note: FNV-1a was developed and tuned for 8-bit sequences. We're using it here /// for 16-bit Unicode chars on the understanding that the majority of chars will /// fit into 8-bits and, therefore, the algorithm will retain its desirable traits /// for generating hash codes. /// internal static int GetFNVHashCode(ReadOnlySpan data) { int hashCode = Hash.FnvOffsetBias; for (int i = 0; i < data.Length; i++) { hashCode = unchecked((hashCode ^ data[i]) * Hash.FnvPrime); } return hashCode; } /// /// Compute the hashcode of a sub-string using FNV-1a /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function /// Note: FNV-1a was developed and tuned for 8-bit sequences. We're using it here /// for 16-bit Unicode chars on the understanding that the majority of chars will /// fit into 8-bits and, therefore, the algorithm will retain its desirable traits /// for generating hash codes. /// /// The input string /// The start index of the first character to hash /// The number of characters, beginning with to hash /// The FNV-1a hash code of the substring beginning at and ending after characters. internal static int GetFNVHashCode(string text, int start, int length) => GetFNVHashCode(text.AsSpan(start, length)); internal static int GetCaseInsensitiveFNVHashCode(string text) { return GetCaseInsensitiveFNVHashCode(text.AsSpan(0, text.Length)); } internal static int GetCaseInsensitiveFNVHashCode(ReadOnlySpan data) { int hashCode = Hash.FnvOffsetBias; for (int i = 0; i < data.Length; i++) { hashCode = unchecked((hashCode ^ CaseInsensitiveComparison.ToLower(data[i])) * Hash.FnvPrime); } return hashCode; } /// /// Compute the hashcode of a sub-string using FNV-1a /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function /// /// The input string /// The start index of the first character to hash /// The FNV-1a hash code of the substring beginning at and ending at the end of the string. internal static int GetFNVHashCode(string text, int start) { return GetFNVHashCode(text, start, length: text.Length - start); } /// /// Compute the hashcode of a string using FNV-1a /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function /// /// The input string /// The FNV-1a hash code of internal static int GetFNVHashCode(string text) { return CombineFNVHash(Hash.FnvOffsetBias, text); } /// /// Compute the hashcode of a string using FNV-1a /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function /// /// The input string /// The FNV-1a hash code of internal static int GetFNVHashCode(System.Text.StringBuilder text) { int hashCode = Hash.FnvOffsetBias; int end = text.Length; for (int i = 0; i < end; i++) { hashCode = unchecked((hashCode ^ text[i]) * Hash.FnvPrime); } return hashCode; } /// /// Compute the hashcode of a sub string using FNV-1a /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function /// /// The input string as a char array /// The start index of the first character to hash /// The number of characters, beginning with to hash /// The FNV-1a hash code of the substring beginning at and ending after characters. internal static int GetFNVHashCode(char[] text, int start, int length) { int hashCode = Hash.FnvOffsetBias; int end = start + length; for (int i = start; i < end; i++) { hashCode = unchecked((hashCode ^ text[i]) * Hash.FnvPrime); } return hashCode; } /// /// Compute the hashcode of a single character using the FNV-1a algorithm /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function /// Note: In general, this isn't any more useful than "char.GetHashCode". However, /// it may be needed if you need to generate the same hash code as a string or /// substring with just a single character. /// /// The character to hash /// The FNV-1a hash code of the character. internal static int GetFNVHashCode(char ch) { return Hash.CombineFNVHash(Hash.FnvOffsetBias, ch); } /// /// Combine a string with an existing FNV-1a hash code /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function /// /// The accumulated hash code /// The string to combine /// The result of combining with using the FNV-1a algorithm internal static int CombineFNVHash(int hashCode, string text) { foreach (char ch in text) { hashCode = unchecked((hashCode ^ ch) * Hash.FnvPrime); } return hashCode; } /// /// Combine a char with an existing FNV-1a hash code /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function /// /// The accumulated hash code /// The new character to combine /// The result of combining with using the FNV-1a algorithm internal static int CombineFNVHash(int hashCode, char ch) { return unchecked((hashCode ^ ch) * Hash.FnvPrime); } } }