diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT
index 5493d48e6c..540b4186c7 100644
--- a/THIRD-PARTY-NOTICES.TXT
+++ b/THIRD-PARTY-NOTICES.TXT
@@ -53,3 +53,30 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+License notice for ComputeSharp
+--------------------------------------------------------
+
+https://github.com/Sergio0694/ComputeSharp/blob/main/LICENSE
+
+MIT License
+
+Copyright (c) 2024 Sergio Pedri
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/src/Analyzers/MSTest.SourceGeneration/Helpers/EquatableArray{T}.cs b/src/Analyzers/MSTest.SourceGeneration/Helpers/EquatableArray{T}.cs
new file mode 100644
index 0000000000..ed10c67aef
--- /dev/null
+++ b/src/Analyzers/MSTest.SourceGeneration/Helpers/EquatableArray{T}.cs
@@ -0,0 +1,190 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under dual-license. See LICENSE.PLATFORMTOOLS.txt file in the project root for full license information.
+
+using System.Collections.Immutable;
+
+namespace MSTest.SourceGeneration.Helpers;
+
+// Copy from https://github.com/Sergio0694/ComputeSharp/blob/120aff270539996ef9fc52fe46561d12da0b89d4/src/ComputeSharp.SourceGeneration/Helpers/EquatableArray%7BT%7D.cs
+
+///
+/// Extensions for .
+///
+internal static class EquatableArray
+{
+ ///
+ /// Creates an instance from a given .
+ ///
+ /// The type of items in the input array.
+ /// The input instance.
+ /// An instance from a given .
+ public static EquatableArray AsEquatableArray(this ImmutableArray array)
+ where T : IEquatable
+ => new(array);
+}
+
+///
+/// An imutable, equatable array. This is equivalent to but with value equality support.
+///
+/// The type of values in the array.
+/// The input to wrap.
+internal readonly struct EquatableArray(ImmutableArray array) : IEquatable>, IEnumerable
+ where T : IEquatable
+{
+ ///
+ /// The underlying array.
+ ///
+ private readonly T[]? _array = Unsafe.As, T[]?>(ref array);
+
+ ///
+ /// Gets a reference to an item at a specified position within the array.
+ ///
+ /// The index of the item to retrieve a reference to.
+ /// A reference to an item at a specified position within the array.
+ public ref readonly T this[int index]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => ref AsImmutableArray().ItemRef(index);
+ }
+
+ ///
+ /// Gets a value indicating whether the current array is empty.
+ ///
+ public bool IsEmpty
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => AsImmutableArray().IsEmpty;
+ }
+
+ ///
+ /// Gets a value indicating whether the current array is default or empty.
+ ///
+ public bool IsDefaultOrEmpty
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => AsImmutableArray().IsDefaultOrEmpty;
+ }
+
+ ///
+ /// Gets the length of the current array.
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => AsImmutableArray().Length;
+ }
+
+ ///
+ public bool Equals(EquatableArray array)
+ => AsSpan().SequenceEqual(array.AsSpan());
+
+ public override bool Equals(object? obj)
+ => obj is EquatableArray array && Equals(this, array);
+
+ ///
+ public override unsafe int GetHashCode()
+ {
+ if (_array is not T[] array)
+ {
+ return 0;
+ }
+
+ HashCode hashCode = default;
+
+ if (typeof(T) == typeof(byte))
+ {
+ ReadOnlySpan span = array;
+ ref T r0 = ref MemoryMarshal.GetReference(span);
+ ref byte r1 = ref Unsafe.As(ref r0);
+
+ fixed (byte* p = &r1)
+ {
+ ReadOnlySpan bytes = new(p, span.Length);
+
+ hashCode.AddBytes(bytes);
+ }
+ }
+ else
+ {
+ foreach (T item in array)
+ {
+ hashCode.Add(item);
+ }
+ }
+
+ return hashCode.ToHashCode();
+ }
+
+ ///
+ /// Gets an instance from the current .
+ ///
+ /// The from the current .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ImmutableArray AsImmutableArray()
+ => Unsafe.As>(ref Unsafe.AsRef(in _array));
+
+ ///
+ /// Creates an instance from a given .
+ ///
+ /// The input instance.
+ /// An instance from a given .
+ public static EquatableArray FromImmutableArray(ImmutableArray array)
+ => new(array);
+
+ ///
+ /// Returns a wrapping the current items.
+ ///
+ /// A wrapping the current items.
+ public ReadOnlySpan AsSpan()
+ => AsImmutableArray().AsSpan();
+
+ ///
+ /// Copies the contents of this instance. to a mutable array.
+ ///
+ /// The newly instantiated array.
+ public T[] ToArray()
+ => [.. AsImmutableArray()];
+
+ ///
+ /// Gets an value to traverse items in the current array.
+ ///
+ /// An value to traverse items in the current array.
+ public ImmutableArray.Enumerator GetEnumerator()
+ => AsImmutableArray().GetEnumerator();
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ => ((IEnumerable)AsImmutableArray()).GetEnumerator();
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ => ((IEnumerable)AsImmutableArray()).GetEnumerator();
+
+ ///
+ /// Implicitly converts an to .
+ ///
+ /// An instance from a given .
+ public static implicit operator EquatableArray(ImmutableArray array) => FromImmutableArray(array);
+
+ ///
+ /// Implicitly converts an to .
+ ///
+ /// An instance from a given .
+ public static implicit operator ImmutableArray(EquatableArray array) => array.AsImmutableArray();
+
+ ///
+ /// Checks whether two values are the same.
+ ///
+ /// The first value.
+ /// The second value.
+ /// Whether and are equal.
+ public static bool operator ==(EquatableArray left, EquatableArray right) => left.Equals(right);
+
+ ///
+ /// Checks whether two values are not the same.
+ ///
+ /// The first value.
+ /// The second value.
+ /// Whether and are not equal.
+ public static bool operator !=(EquatableArray left, EquatableArray right) => !left.Equals(right);
+}
diff --git a/src/Analyzers/MSTest.SourceGeneration/Helpers/HashCode.cs b/src/Analyzers/MSTest.SourceGeneration/Helpers/HashCode.cs
new file mode 100644
index 0000000000..ec41552ded
--- /dev/null
+++ b/src/Analyzers/MSTest.SourceGeneration/Helpers/HashCode.cs
@@ -0,0 +1,488 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under dual-license. See LICENSE.PLATFORMTOOLS.txt file in the project root for full license information.
+
+using System.ComponentModel;
+using System.Security.Cryptography;
+
+namespace System;
+
+///
+/// A polyfill type that mirrors some methods from on .7.
+///
+internal struct HashCode
+{
+ private const uint Prime1 = 2654435761U;
+ private const uint Prime2 = 2246822519U;
+ private const uint Prime3 = 3266489917U;
+ private const uint Prime4 = 668265263U;
+ private const uint Prime5 = 374761393U;
+
+ private static readonly uint Seed = GenerateGlobalSeed();
+
+ private uint _v1;
+ private uint _v2;
+ private uint _v3;
+ private uint _v4;
+ private uint _queue1;
+ private uint _queue2;
+ private uint _queue3;
+ private uint _length;
+
+ ///
+ /// Initializes the default seed.
+ ///
+ /// A random seed.
+ private static unsafe uint GenerateGlobalSeed()
+ {
+ byte[] bytes = new byte[4];
+
+ RandomNumberGenerator.Create().GetBytes(bytes);
+
+ return BitConverter.ToUInt32(bytes, 0);
+ }
+
+ ///
+ /// Combines a value into a hash code.
+ ///
+ /// The type of the value to combine into the hash code.
+ /// The value to combine into the hash code.
+ /// The hash code that represents the value.
+ public static int Combine(T1 value)
+ {
+ uint hc1 = (uint)(value?.GetHashCode() ?? 0);
+ uint hash = MixEmptyState();
+
+ hash += 4;
+ hash = QueueRound(hash, hc1);
+ hash = MixFinal(hash);
+
+ return (int)hash;
+ }
+
+ ///
+ /// Combines two values into a hash code.
+ ///
+ /// The type of the first value to combine into the hash code.
+ /// The type of the second value to combine into the hash code.
+ /// The first value to combine into the hash code.
+ /// The second value to combine into the hash code.
+ /// The hash code that represents the values.
+ public static int Combine(T1 value1, T2 value2)
+ {
+ uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ uint hash = MixEmptyState();
+
+ hash += 8;
+ hash = QueueRound(hash, hc1);
+ hash = QueueRound(hash, hc2);
+ hash = MixFinal(hash);
+
+ return (int)hash;
+ }
+
+ ///
+ /// Combines three values into a hash code.
+ ///
+ /// The type of the first value to combine into the hash code.
+ /// The type of the second value to combine into the hash code.
+ /// The type of the third value to combine into the hash code.
+ /// The first value to combine into the hash code.
+ /// The second value to combine into the hash code.
+ /// The third value to combine into the hash code.
+ /// The hash code that represents the values.
+ public static int Combine(T1 value1, T2 value2, T3 value3)
+ {
+ uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ uint hash = MixEmptyState();
+
+ hash += 12;
+ hash = QueueRound(hash, hc1);
+ hash = QueueRound(hash, hc2);
+ hash = QueueRound(hash, hc3);
+ hash = MixFinal(hash);
+
+ return (int)hash;
+ }
+
+ ///
+ /// Combines four values into a hash code.
+ ///
+ /// The type of the first value to combine into the hash code.
+ /// The type of the second value to combine into the hash code.
+ /// The type of the third value to combine into the hash code.
+ /// The type of the fourth value to combine into the hash code.
+ /// The first value to combine into the hash code.
+ /// The second value to combine into the hash code.
+ /// The third value to combine into the hash code.
+ /// The fourth value to combine into the hash code.
+ /// The hash code that represents the values.
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4)
+ {
+ uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
+
+ Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
+
+ v1 = Round(v1, hc1);
+ v2 = Round(v2, hc2);
+ v3 = Round(v3, hc3);
+ v4 = Round(v4, hc4);
+
+ uint hash = MixState(v1, v2, v3, v4);
+
+ hash += 16;
+ hash = MixFinal(hash);
+
+ return (int)hash;
+ }
+
+ ///
+ /// Combines five values into a hash code.
+ ///
+ /// The type of the first value to combine into the hash code.
+ /// The type of the second value to combine into the hash code.
+ /// The type of the third value to combine into the hash code.
+ /// The type of the fourth value to combine into the hash code.
+ /// The type of the fifth value to combine into the hash code.
+ /// The first value to combine into the hash code.
+ /// The second value to combine into the hash code.
+ /// The third value to combine into the hash code.
+ /// The fourth value to combine into the hash code.
+ /// The fifth value to combine into the hash code.
+ /// The hash code that represents the values.
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5)
+ {
+ uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
+ uint hc5 = (uint)(value5?.GetHashCode() ?? 0);
+
+ Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
+
+ v1 = Round(v1, hc1);
+ v2 = Round(v2, hc2);
+ v3 = Round(v3, hc3);
+ v4 = Round(v4, hc4);
+
+ uint hash = MixState(v1, v2, v3, v4);
+
+ hash += 20;
+ hash = QueueRound(hash, hc5);
+ hash = MixFinal(hash);
+
+ return (int)hash;
+ }
+
+ ///
+ /// Combines six values into a hash code.
+ ///
+ /// The type of the first value to combine into the hash code.
+ /// The type of the second value to combine into the hash code.
+ /// The type of the third value to combine into the hash code.
+ /// The type of the fourth value to combine into the hash code.
+ /// The type of the fifth value to combine into the hash code.
+ /// The type of the sixth value to combine into the hash code.
+ /// The first value to combine into the hash code.
+ /// The second value to combine into the hash code.
+ /// The third value to combine into the hash code.
+ /// The fourth value to combine into the hash code.
+ /// The fifth value to combine into the hash code.
+ /// The sixth value to combine into the hash code.
+ /// The hash code that represents the values.
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6)
+ {
+ uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
+ uint hc5 = (uint)(value5?.GetHashCode() ?? 0);
+ uint hc6 = (uint)(value6?.GetHashCode() ?? 0);
+
+ Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
+
+ v1 = Round(v1, hc1);
+ v2 = Round(v2, hc2);
+ v3 = Round(v3, hc3);
+ v4 = Round(v4, hc4);
+
+ uint hash = MixState(v1, v2, v3, v4);
+
+ hash += 24;
+ hash = QueueRound(hash, hc5);
+ hash = QueueRound(hash, hc6);
+ hash = MixFinal(hash);
+
+ return (int)hash;
+ }
+
+ ///
+ /// Combines seven values into a hash code.
+ ///
+ /// The type of the first value to combine into the hash code.
+ /// The type of the second value to combine into the hash code.
+ /// The type of the third value to combine into the hash code.
+ /// The type of the fourth value to combine into the hash code.
+ /// The type of the fifth value to combine into the hash code.
+ /// The type of the sixth value to combine into the hash code.
+ /// The type of the seventh value to combine into the hash code.
+ /// The first value to combine into the hash code.
+ /// The second value to combine into the hash code.
+ /// The third value to combine into the hash code.
+ /// The fourth value to combine into the hash code.
+ /// The fifth value to combine into the hash code.
+ /// The sixth value to combine into the hash code.
+ /// The seventh value to combine into the hash code.
+ /// The hash code that represents the values.
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7)
+ {
+ uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
+ uint hc5 = (uint)(value5?.GetHashCode() ?? 0);
+ uint hc6 = (uint)(value6?.GetHashCode() ?? 0);
+ uint hc7 = (uint)(value7?.GetHashCode() ?? 0);
+
+ Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
+
+ v1 = Round(v1, hc1);
+ v2 = Round(v2, hc2);
+ v3 = Round(v3, hc3);
+ v4 = Round(v4, hc4);
+
+ uint hash = MixState(v1, v2, v3, v4);
+
+ hash += 28;
+ hash = QueueRound(hash, hc5);
+ hash = QueueRound(hash, hc6);
+ hash = QueueRound(hash, hc7);
+ hash = MixFinal(hash);
+
+ return (int)hash;
+ }
+
+ ///
+ /// Combines eight values into a hash code.
+ ///
+ /// The type of the first value to combine into the hash code.
+ /// The type of the second value to combine into the hash code.
+ /// The type of the third value to combine into the hash code.
+ /// The type of the fourth value to combine into the hash code.
+ /// The type of the fifth value to combine into the hash code.
+ /// The type of the sixth value to combine into the hash code.
+ /// The type of the seventh value to combine into the hash code.
+ /// The type of the eighth value to combine into the hash code.
+ /// The first value to combine into the hash code.
+ /// The second value to combine into the hash code.
+ /// The third value to combine into the hash code.
+ /// The fourth value to combine into the hash code.
+ /// The fifth value to combine into the hash code.
+ /// The sixth value to combine into the hash code.
+ /// The seventh value to combine into the hash code.
+ /// The eighth value to combine into the hash code.
+ /// The hash code that represents the values.
+ public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8)
+ {
+ uint hc1 = (uint)(value1?.GetHashCode() ?? 0);
+ uint hc2 = (uint)(value2?.GetHashCode() ?? 0);
+ uint hc3 = (uint)(value3?.GetHashCode() ?? 0);
+ uint hc4 = (uint)(value4?.GetHashCode() ?? 0);
+ uint hc5 = (uint)(value5?.GetHashCode() ?? 0);
+ uint hc6 = (uint)(value6?.GetHashCode() ?? 0);
+ uint hc7 = (uint)(value7?.GetHashCode() ?? 0);
+ uint hc8 = (uint)(value8?.GetHashCode() ?? 0);
+
+ Initialize(out uint v1, out uint v2, out uint v3, out uint v4);
+
+ v1 = Round(v1, hc1);
+ v2 = Round(v2, hc2);
+ v3 = Round(v3, hc3);
+ v4 = Round(v4, hc4);
+
+ v1 = Round(v1, hc5);
+ v2 = Round(v2, hc6);
+ v3 = Round(v3, hc7);
+ v4 = Round(v4, hc8);
+
+ uint hash = MixState(v1, v2, v3, v4);
+
+ hash += 32;
+ hash = MixFinal(hash);
+
+ return (int)hash;
+ }
+
+ ///
+ /// Adds a single value to the current hash.
+ ///
+ /// The type of the value to add into the hash code.
+ /// The value to add into the hash code.
+ public void Add(T value)
+ => Add(value?.GetHashCode() ?? 0);
+
+ ///
+ /// Adds a single value to the current hash.
+ ///
+ /// The type of the value to add into the hash code.
+ /// The value to add into the hash code.
+ /// The instance to use.
+ public void Add(T value, IEqualityComparer? comparer)
+ => Add(value is null ? 0 : (comparer?.GetHashCode(value) ?? value.GetHashCode()));
+
+ ///
+ /// Adds a span of bytes to the hash code.
+ ///
+ /// The span.
+ public void AddBytes(ReadOnlySpan value)
+ {
+ ref byte pos = ref MemoryMarshal.GetReference(value);
+ ref byte end = ref Unsafe.Add(ref pos, value.Length);
+
+ while ((nint)Unsafe.ByteOffset(ref pos, ref end) >= sizeof(int))
+ {
+ Add(Unsafe.ReadUnaligned(ref pos));
+ pos = ref Unsafe.Add(ref pos, sizeof(int));
+ }
+
+ while (Unsafe.IsAddressLessThan(ref pos, ref end))
+ {
+ Add((int)pos);
+ pos = ref Unsafe.Add(ref pos, 1);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4)
+ {
+ v1 = Seed + Prime1 + Prime2;
+ v2 = Seed + Prime2;
+ v3 = Seed;
+ v4 = Seed - Prime1;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint Round(uint hash, uint input)
+ => RotateLeft(hash + (input * Prime2), 13) * Prime1;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint QueueRound(uint hash, uint queuedValue)
+ => RotateLeft(hash + (queuedValue * Prime3), 17) * Prime4;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint MixState(uint v1, uint v2, uint v3, uint v4)
+ => RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint MixEmptyState()
+ => Seed + Prime5;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint MixFinal(uint hash)
+ {
+ hash ^= hash >> 15;
+ hash *= Prime2;
+ hash ^= hash >> 13;
+ hash *= Prime3;
+ hash ^= hash >> 16;
+
+ return hash;
+ }
+
+ private void Add(int value)
+ {
+ uint val = (uint)value;
+ uint previousLength = _length++;
+ uint position = previousLength % 4;
+
+ if (position == 0)
+ {
+ _queue1 = val;
+ }
+ else if (position == 1)
+ {
+ _queue2 = val;
+ }
+ else if (position == 2)
+ {
+ _queue3 = val;
+ }
+ else
+ {
+ if (previousLength == 3)
+ {
+ Initialize(out _v1, out _v2, out _v3, out _v4);
+ }
+
+ _v1 = Round(_v1, _queue1);
+ _v2 = Round(_v2, _queue2);
+ _v3 = Round(_v3, _queue3);
+ _v4 = Round(_v4, val);
+ }
+ }
+
+ ///
+ /// Gets the resulting hashcode from the current instance.
+ ///
+ /// The resulting hashcode from the current instance.
+ public readonly int ToHashCode()
+ {
+ uint length = _length;
+ uint position = length % 4;
+ uint hash = length < 4 ? MixEmptyState() : MixState(_v1, _v2, _v3, _v4);
+
+ hash += length * 4;
+
+ if (position > 0)
+ {
+ hash = QueueRound(hash, _queue1);
+
+ if (position > 1)
+ {
+ hash = QueueRound(hash, _queue2);
+
+ if (position > 2)
+ {
+ hash = QueueRound(hash, _queue3);
+ }
+ }
+ }
+
+ hash = MixFinal(hash);
+
+ return (int)hash;
+ }
+
+ ///
+ [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member
+ public override readonly int GetHashCode()
+#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member
+ => throw new NotSupportedException();
+
+ ///
+ [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member
+ public override readonly bool Equals(object? obj)
+#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member
+ => throw new NotSupportedException();
+
+ ///
+ /// Rotates the specified value left by the specified number of bits.
+ /// Similar in behavior to the x86 instruction ROL.
+ ///
+ /// The value to rotate.
+ /// The number of bits to rotate by.
+ /// Any value outside the range [0..31] is treated as congruent mod 32.
+ /// The rotated value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static uint RotateLeft(uint value, int offset)
+ => (value << offset) | (value >> (32 - offset));
+}
diff --git a/src/Analyzers/MSTest.SourceGeneration/MSTest.SourceGeneration.csproj b/src/Analyzers/MSTest.SourceGeneration/MSTest.SourceGeneration.csproj
index 96fac5b926..f2f228024e 100644
--- a/src/Analyzers/MSTest.SourceGeneration/MSTest.SourceGeneration.csproj
+++ b/src/Analyzers/MSTest.SourceGeneration/MSTest.SourceGeneration.csproj
@@ -19,6 +19,7 @@
$(MSTestEnginePreReleaseVersionLabel)
true
true
+ true
diff --git a/src/Analyzers/MSTest.SourceGeneration/ObjectModels/TestMethodInfo.cs b/src/Analyzers/MSTest.SourceGeneration/ObjectModels/TestMethodInfo.cs
index ec9d1a97c1..0a8c842d3a 100644
--- a/src/Analyzers/MSTest.SourceGeneration/ObjectModels/TestMethodInfo.cs
+++ b/src/Analyzers/MSTest.SourceGeneration/ObjectModels/TestMethodInfo.cs
@@ -6,23 +6,25 @@
using Microsoft.CodeAnalysis;
using Microsoft.Testing.Framework.SourceGeneration.Helpers;
+using MSTest.SourceGeneration.Helpers;
+
namespace Microsoft.Testing.Framework.SourceGeneration.ObjectModels;
-internal sealed class TestMethodInfo
+internal sealed record class TestMethodInfo
{
internal const string TestArgumentsEntryTypeName = "MSTF::InternalUnsafeTestArgumentsEntry";
private const string CtorVariableName = "instance";
private const string TestExecutionContextVariableName = "testExecutionContext";
private const string DataVariableName = "data";
private const string DataDotArgumentsMemberAccessName = DataVariableName + ".Arguments";
- private readonly ImmutableArray<(string FilePath, int StartLine, int EndLine)> _declarationReferences;
+ private readonly EquatableArray<(string FilePath, int StartLine, int EndLine)> _declarationReferences;
private readonly string _methodName;
private readonly string _declaringAssemblyName;
private readonly string _usingTypeFullyQualifiedName;
private readonly bool _isAsync;
- private readonly ImmutableArray<(string Key, string? Value)> _testProperties;
+ private readonly EquatableArray<(string Key, string? Value)> _testProperties;
private readonly TimeSpan? _testExecutionTimeout;
- private readonly ImmutableArray<(string RuleId, string Description)> _invocationPragmas;
+ private readonly EquatableArray<(string RuleId, string Description)> _invocationPragmas;
private readonly string _methodIdentifierAssemblyName;
private readonly string _methodIdentifierNamespace;
private readonly string _methodIdentifierTypeName;
diff --git a/src/Analyzers/MSTest.SourceGeneration/ObjectModels/TestTypeInfo.cs b/src/Analyzers/MSTest.SourceGeneration/ObjectModels/TestTypeInfo.cs
index 00b5de7133..60d46150ed 100644
--- a/src/Analyzers/MSTest.SourceGeneration/ObjectModels/TestTypeInfo.cs
+++ b/src/Analyzers/MSTest.SourceGeneration/ObjectModels/TestTypeInfo.cs
@@ -8,15 +8,17 @@
using Microsoft.CodeAnalysis;
using Microsoft.Testing.Framework.SourceGeneration.Helpers;
+using MSTest.SourceGeneration.Helpers;
+
namespace Microsoft.Testing.Framework.SourceGeneration.ObjectModels;
-internal sealed class TestTypeInfo
+internal sealed record class TestTypeInfo
{
private readonly string _name;
private readonly string _containingAssemblyName;
- private readonly ImmutableArray<(string FilePath, int StartLine, int EndLine)> _declarationReferences;
+ private readonly EquatableArray<(string FilePath, int StartLine, int EndLine)> _declarationReferences;
- internal ImmutableArray TestMethodNodes { get; }
+ internal EquatableArray TestMethodNodes { get; }
public TimeSpan? TestExecutionTimeout { get; }