diff --git a/1.3/Assemblies/1Fishery.dll b/1.3/Assemblies/1Fishery.dll index bfc5879..ae26877 100644 Binary files a/1.3/Assemblies/1Fishery.dll and b/1.3/Assemblies/1Fishery.dll differ diff --git a/1.3/Assemblies/1Fishery.pdb b/1.3/Assemblies/1Fishery.pdb new file mode 100644 index 0000000..efc3455 Binary files /dev/null and b/1.3/Assemblies/1Fishery.pdb differ diff --git a/1.3/Assemblies/IndexRange.dll b/1.3/Assemblies/IndexRange.dll deleted file mode 100644 index be06f22..0000000 Binary files a/1.3/Assemblies/IndexRange.dll and /dev/null differ diff --git a/1.4/Assemblies/1Fishery.dll b/1.4/Assemblies/1Fishery.dll index 6fbb1f3..25f63b3 100644 Binary files a/1.4/Assemblies/1Fishery.dll and b/1.4/Assemblies/1Fishery.dll differ diff --git a/1.4/Assemblies/1Fishery.pdb b/1.4/Assemblies/1Fishery.pdb new file mode 100644 index 0000000..0904abb Binary files /dev/null and b/1.4/Assemblies/1Fishery.pdb differ diff --git a/1.4/Assemblies/IndexRange.dll b/1.4/Assemblies/IndexRange.dll deleted file mode 100644 index be06f22..0000000 Binary files a/1.4/Assemblies/IndexRange.dll and /dev/null differ diff --git a/About/About.xml b/About/About.xml index d71e95d..53edef4 100644 --- a/About/About.xml +++ b/About/About.xml @@ -11,7 +11,6 @@
  • Ludeon.RimWorld
  • me.samboycoding.betterloading
  • -
  • zetrith.prepatcher
  • brrainz.harmony
  • diff --git a/README.md b/README.md index c067c6f..6ed94ad 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ # Fishery Modding Library for RimWorld. Used by Performance Fish. -Useful functions include various abstractions around IL in FishTranspiler, to make writing of transpilers easier and more robust. A couple collections like Dictionary and Span have extension methods for fast and convenient lookups. Some functionalities of Microsoft's Community Toolkit are included, expanded upon and modified to better fit Unity. Equality includes generic extension methods to use in place of Equals on structs without memory allocations. Convert provides a faster, generic alternative to System.Convert.ToType. FisheryLib.Reflection provides a couple reflection helpers, Aliases has a methodof function intended to be utilized with `global using static Aliases` to directly retrieve method infos from static method groups. +Useful functions include various abstractions around IL in FishTranspiler, to make writing of transpilers easier and more robust. A couple collections like Dictionary and Span have extension methods for fast and convenient lookups. Some functionalities of Microsoft's Community Toolkit are included, expanded upon and modified to better fit Unity. Equality includes generic extension methods to use in place of Equals on structs without memory allocations. Convert provides a faster, generic alternative to System.Convert.ToType. FisheryLib.Reflection provides a couple reflection helpers, Aliases has a methodof function intended to be utilized with `global using static Aliases` to directly retrieve method infos from static method groups. +Additionally this includes a fast and much more versatile System.Collections.Generic.Dictionary replacement in FisheryLib.Collections.FishTable, loosely based on Malte Skarupke's ska::bytell_hash_map, as well as several extensions and helpers for Mono.Cecil, which is utilized by Prepatcher. \ No newline at end of file diff --git a/Source/FisheryLib/.editorconfig b/Source/FisheryLib/.editorconfig index 14c2131..729881f 100644 --- a/Source/FisheryLib/.editorconfig +++ b/Source/FisheryLib/.editorconfig @@ -1,6 +1,6 @@ [*.cs] -file_header_template = Copyright (c) 2022 bradson\nThis Source Code Form is subject to the terms of the Mozilla Public\nLicense, v. 2.0. If a copy of the MPL was not distributed with this\nfile, You can obtain one at https://mozilla.org/MPL/2.0/. +file_header_template = Copyright (c) 2022 bradson\nThis Source Code Form is subject to the terms of the Mozilla Public\nLicense, v.2.0.If a copy of the MPL was not distributed with this\nfile, You can obtain one at https://mozilla.org/MPL/2.0/. # CA2211: Non-constant fields should not be visible dotnet_diagnostic.CA2211.severity = none @@ -47,6 +47,7 @@ dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_style_prefer_compound_assignment = true:suggestion dotnet_style_prefer_simplified_interpolation = true:suggestion dotnet_style_namespace_match_folder = true:suggestion + [*.cs] #### Naming styles #### @@ -72,27 +73,27 @@ dotnet_naming_symbols.constant.required_modifiers = const dotnet_naming_symbols.private_or_protected_field.applicable_kinds = field dotnet_naming_symbols.private_or_protected_field.applicable_accessibilities = private, protected, protected_internal, private_protected -dotnet_naming_symbols.private_or_protected_field.required_modifiers = +dotnet_naming_symbols.private_or_protected_field.required_modifiers = dotnet_naming_symbols.public_or_internal_field.applicable_kinds = field dotnet_naming_symbols.public_or_internal_field.applicable_accessibilities = public, internal -dotnet_naming_symbols.public_or_internal_field.required_modifiers = +dotnet_naming_symbols.public_or_internal_field.required_modifiers = # Naming styles -dotnet_naming_style.all_upper.required_prefix = -dotnet_naming_style.all_upper.required_suffix = +dotnet_naming_style.all_upper.required_prefix = +dotnet_naming_style.all_upper.required_suffix = dotnet_naming_style.all_upper.word_separator = _ dotnet_naming_style.all_upper.capitalization = all_upper dotnet_naming_style.camel_case_with__.required_prefix = _ -dotnet_naming_style.camel_case_with__.required_suffix = -dotnet_naming_style.camel_case_with__.word_separator = +dotnet_naming_style.camel_case_with__.required_suffix = +dotnet_naming_style.camel_case_with__.word_separator = dotnet_naming_style.camel_case_with__.capitalization = camel_case -dotnet_naming_style.camel_case.required_prefix = -dotnet_naming_style.camel_case.required_suffix = -dotnet_naming_style.camel_case.word_separator = +dotnet_naming_style.camel_case.required_prefix = +dotnet_naming_style.camel_case.required_suffix = +dotnet_naming_style.camel_case.word_separator = dotnet_naming_style.camel_case.capitalization = camel_case dotnet_diagnostic.CA1805.severity = none csharp_style_prefer_tuple_swap = true:suggestion @@ -138,31 +139,31 @@ dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case dotnet_naming_symbols.interface.applicable_kinds = interface dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.interface.required_modifiers = +dotnet_naming_symbols.interface.required_modifiers = dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.types.required_modifiers = +dotnet_naming_symbols.types.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.non_field_members.required_modifiers = +dotnet_naming_symbols.non_field_members.required_modifiers = # Naming styles dotnet_naming_style.begins_with_i.required_prefix = I -dotnet_naming_style.begins_with_i.required_suffix = -dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.capitalization = pascal_case -dotnet_naming_style.pascal_case.required_prefix = -dotnet_naming_style.pascal_case.required_suffix = -dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case -dotnet_naming_style.pascal_case.required_prefix = -dotnet_naming_style.pascal_case.required_suffix = -dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_diagnostic.CA1838.severity = suggestion dotnet_diagnostic.CA1848.severity = suggestion diff --git a/Source/FisheryLib/Aliases.cs b/Source/FisheryLib/Aliases.cs index fbaade2..bbc7b5f 100644 --- a/Source/FisheryLib/Aliases.cs +++ b/Source/FisheryLib/Aliases.cs @@ -5,12 +5,15 @@ global using static FisheryLib.Aliases; using System.Runtime.CompilerServices; +using JetBrains.Annotations; namespace FisheryLib; +[PublicAPI] [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "alias")] public static class Aliases { [MethodImpl(MethodImplOptions.AggressiveInlining)] + // ReSharper disable once InconsistentNaming public static MethodInfo methodof(Delegate method) => method.Method; } \ No newline at end of file diff --git a/Source/FisheryLib/ArraySortHelper.cs b/Source/FisheryLib/ArraySortHelper.cs index 7e91c41..d1253dc 100644 --- a/Source/FisheryLib/ArraySortHelper.cs +++ b/Source/FisheryLib/ArraySortHelper.cs @@ -1,18 +1,21 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using UnityEngine; +// ReSharper disable PossibleUnintendedReferenceComparison namespace FisheryLib; + #region ArraySortHelper for single arrays +#pragma warning disable CS8500 internal sealed class ArraySortHelper : IArraySortHelper { public static IArraySortHelper Default { get; } = CreateArraySortHelper(); private static IArraySortHelper CreateArraySortHelper() => typeof(IComparable).IsAssignableFrom(typeof(T)) - ? (IArraySortHelper)Activator.CreateInstance(typeof(GenericArraySortHelper<>).MakeGenericType(typeof(T))) - : new ArraySortHelper(); + ? (IArraySortHelper)Activator.CreateInstance(typeof(GenericArraySortHelper<>).MakeGenericType(typeof(T))) + : new ArraySortHelper(); #region IArraySortHelper Members @@ -61,9 +64,7 @@ internal sealed class ArraySortHelper : IArraySortHelper Debug.Assert(i != j); if (comparer(keys[i], keys[j]) > 0) - { (keys[j], keys[i]) = (keys[i], keys[j]); - } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -79,9 +80,7 @@ internal sealed class ArraySortHelper : IArraySortHelper Debug.Assert(comparer != null); if (keys.Length > 1) - { IntroSort(keys, 2 * (BitOperations.Log2((uint)keys.Length) + 1), comparer!); - } } private static void IntroSort(Span keys, int depthLimit, Comparison comparer) @@ -95,7 +94,6 @@ internal sealed class ArraySortHelper : IArraySortHelper { if (partitionSize <= SortUtils.INTROSORT_SIZE_THRESHOLD) { - if (partitionSize == 2) { SwapIfGreater(keys, comparer!, 0, 1); @@ -119,6 +117,7 @@ internal sealed class ArraySortHelper : IArraySortHelper HeapSort(keys[..partitionSize], comparer!); return; } + depthLimit--; var p = PickPivotAndPartition(keys[..partitionSize], comparer!); @@ -141,12 +140,14 @@ internal sealed class ArraySortHelper : IArraySortHelper // Sort lo, mid and hi appropriately, then pick mid as the pivot. SwapIfGreater(keys, comparer!, 0, middle); // swap the low with the mid point - SwapIfGreater(keys, comparer!, 0, hi); // swap the low with the high + SwapIfGreater(keys, comparer!, 0, hi); // swap the low with the high SwapIfGreater(keys, comparer!, middle, hi); // swap the middle with the high var pivot = keys[middle]; Swap(keys, middle, hi - 1); - int left = 0, right = hi - 1; // We already partitioned lo and hi and put the pivot in hi - 1. And we pre-increment & decrement below. + int left = 0, + right = hi + - 1; // We already partitioned lo and hi and put the pivot in hi - 1. And we pre-increment & decrement below. while (left < right) { @@ -163,9 +164,7 @@ internal sealed class ArraySortHelper : IArraySortHelper // Put pivot in the right location. if (left != hi - 1) - { Swap(keys, left, hi - 1); - } return left; } @@ -176,9 +175,7 @@ internal sealed class ArraySortHelper : IArraySortHelper var n = keys.Length; for (var i = n >> 1; i >= 1; i--) - { DownHeap(keys, i, n, comparer!); - } for (var i = n; i > 1; i--) { @@ -196,9 +193,7 @@ internal sealed class ArraySortHelper : IArraySortHelper { var child = 2 * i; if (child < n && comparer!(keys[child - 1], keys[child]) < 0) - { child++; - } if (!(comparer!(d, keys[child - 1]) < 0)) break; @@ -241,29 +236,25 @@ internal sealed class GenericArraySortHelper : IArraySortHelper { if (comparer == null || comparer == Comparer.Default) { - if (keys.Length > 1) - { - // For floating-point, do a pre-pass to move all NaNs to the beginning - // so that we can do an optimized comparison as part of the actual sort - // on the remainder of the values. - if (typeof(T) == typeof(double) || - typeof(T) == typeof(float)) - { - var nanLeft = SortUtils.MoveNansToFront(keys, default(Span)); - if (nanLeft == keys.Length) - { - return; - } - keys = keys[nanLeft..]; - } + if (keys.Length <= 1) + return; - IntroSort(keys, 2 * (BitOperations.Log2((uint)keys.Length) + 1)); + // For floating-point, do a pre-pass to move all NaNs to the beginning + // so that we can do an optimized comparison as part of the actual sort + // on the remainder of the values. + if (typeof(T) == typeof(double) || typeof(T) == typeof(float)) + { + var nanLeft = SortUtils.MoveNansToFront(keys, default(Span)); + if (nanLeft == keys.Length) + return; + + keys = keys[nanLeft..]; } + + IntroSort(keys, 2 * (BitOperations.Log2((uint)keys.Length) + 1)); } else - { ArraySortHelper.IntrospectiveSort(keys, comparer.Compare); - } } catch (IndexOutOfRangeException) { @@ -279,12 +270,10 @@ internal sealed class GenericArraySortHelper : IArraySortHelper /// Swaps the values in the two references if the first is greater than the second. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void SwapIfGreater(ref T i, ref T j) + private static void SwapIfGreater([AllowNull] ref T i, ref T j) { if (i != null && GreaterThan(ref i, ref j)) - { Swap(ref i, ref j); - } } /// Swaps the values in the two references, regardless of whether the two references are the same. @@ -333,6 +322,7 @@ internal sealed class GenericArraySortHelper : IArraySortHelper HeapSort(keys[..partitionSize]); return; } + depthLimit--; var p = PickPivotAndPartition(keys[..partitionSize]); @@ -343,7 +333,7 @@ internal sealed class GenericArraySortHelper : IArraySortHelper } } - private static int PickPivotAndPartition(Span keys) + private static unsafe int PickPivotAndPartition(Span keys) { Debug.Assert(keys.Length >= SortUtils.INTROSORT_SIZE_THRESHOLD); @@ -361,38 +351,39 @@ internal sealed class GenericArraySortHelper : IArraySortHelper Swap(ref middleRef, ref nextToLastRef); // Walk the left and right pointers, swapping elements as necessary, until they cross. + // ReSharper disable once RedundantSuppressNullableWarningExpression ref T leftRef = ref zeroRef!, rightRef = ref nextToLastRef!; while (Unsafe.IsAddressLessThan(ref leftRef, ref rightRef)) { if (pivot == null) { - while (Unsafe.IsAddressLessThan(ref leftRef!, ref nextToLastRef) && (leftRef = ref Unsafe.Add(ref leftRef, 1)) == null) + while (Unsafe.IsAddressLessThan(ref leftRef!, ref nextToLastRef) + && (leftRef = ref Unsafe.Add(ref leftRef, 1)) == null) ; - while (Unsafe.IsAddressGreaterThan(ref rightRef, ref zeroRef) && (rightRef = ref Unsafe.Add(ref rightRef, -1)) != null) + while (Unsafe.IsAddressGreaterThan(ref rightRef, ref zeroRef) + && (rightRef = ref Unsafe.Add(ref rightRef, -1)) != null) ; } else { - while (Unsafe.IsAddressLessThan(ref leftRef, ref nextToLastRef) && GreaterThan(ref pivot, ref leftRef = ref Unsafe.Add(ref leftRef, 1))) + while (Unsafe.IsAddressLessThan(ref leftRef, ref nextToLastRef) + && GreaterThan(ref pivot, ref leftRef = ref Unsafe.Add(ref leftRef, 1))) ; - while (Unsafe.IsAddressGreaterThan(ref rightRef, ref zeroRef) && LessThan(ref pivot, ref rightRef = ref Unsafe.Add(ref rightRef, -1))) + while (Unsafe.IsAddressGreaterThan(ref rightRef, ref zeroRef) + && LessThan(ref pivot, ref rightRef = ref Unsafe.Add(ref rightRef, -1))) ; } if (!Unsafe.IsAddressLessThan(ref leftRef, ref rightRef!)) - { break; - } Swap(ref leftRef, ref rightRef); } // Put the pivot in the correct location. if (!Unsafe.AreSame(ref leftRef, ref nextToLastRef)) - { Swap(ref leftRef, ref nextToLastRef); - } - return (int)((nint)Unsafe.ByteOffset(ref zeroRef, ref leftRef) / Unsafe.SizeOf()); + return (int)((nint)Unsafe.ByteOffset(ref zeroRef, ref leftRef) / sizeof(T)); } private static void HeapSort(Span keys) @@ -401,9 +392,7 @@ internal sealed class GenericArraySortHelper : IArraySortHelper var n = keys.Length; for (var i = n >> 1; i >= 1; i--) - { DownHeap(keys, i, n); - } for (var i = n; i > 1; i--) { @@ -419,9 +408,7 @@ internal sealed class GenericArraySortHelper : IArraySortHelper { var child = 2 * i; if (child < n && (keys[child - 1] == null || LessThan(ref keys[child - 1], ref keys[child]))) - { child++; - } if (keys[child - 1] == null || !LessThan(ref d, ref keys[child - 1])) break; @@ -442,7 +429,8 @@ internal sealed class GenericArraySortHelper : IArraySortHelper var j = i; while (j >= 0 && (t == null || LessThan(ref t, ref Unsafe.Add(ref MemoryMarshal.GetReference(keys), j)))) { - Unsafe.Add(ref MemoryMarshal.GetReference(keys), j + 1) = Unsafe.Add(ref MemoryMarshal.GetReference(keys), j); + Unsafe.Add(ref MemoryMarshal.GetReference(keys), j + 1) + = Unsafe.Add(ref MemoryMarshal.GetReference(keys), j); j--; } @@ -460,58 +448,34 @@ internal sealed class GenericArraySortHelper : IArraySortHelper [MethodImpl(MethodImplOptions.AggressiveInlining)] // compiles to a single comparison or method call private static bool LessThan(ref T left, ref T right) - => typeof(T) == typeof(byte) - ? (byte)(object)left < (byte)(object)right - : typeof(T) == typeof(sbyte) - ? (sbyte)(object)left < (sbyte)(object)right - : typeof(T) == typeof(ushort) - ? (ushort)(object)left < (ushort)(object)right - : typeof(T) == typeof(short) - ? (short)(object)left < (short)(object)right - : typeof(T) == typeof(uint) - ? (uint)(object)left < (uint)(object)right - : typeof(T) == typeof(int) - ? (int)(object)left < (int)(object)right - : typeof(T) == typeof(ulong) - ? (ulong)(object)left < (ulong)(object)right - : typeof(T) == typeof(long) - ? (long)(object)left < (long)(object)right - : typeof(T) == typeof(nuint) - ? (nuint)(object)left < (nuint)(object)right - : typeof(T) == typeof(nint) - ? (nint)(object)left < (nint)(object)right - : typeof(T) == typeof(float) - ? (float)(object)left < (float)(object)right - : typeof(T) == typeof(double) - ? (double)(object)left < (double)(object)right + => typeof(T) == typeof(byte) ? (byte)(object)left < (byte)(object)right + : typeof(T) == typeof(sbyte) ? (sbyte)(object)left < (sbyte)(object)right + : typeof(T) == typeof(ushort) ? (ushort)(object)left < (ushort)(object)right + : typeof(T) == typeof(short) ? (short)(object)left < (short)(object)right + : typeof(T) == typeof(uint) ? (uint)(object)left < (uint)(object)right + : typeof(T) == typeof(int) ? (int)(object)left < (int)(object)right + : typeof(T) == typeof(ulong) ? (ulong)(object)left < (ulong)(object)right + : typeof(T) == typeof(long) ? (long)(object)left < (long)(object)right + : typeof(T) == typeof(nuint) ? (nuint)(object)left < (nuint)(object)right + : typeof(T) == typeof(nint) ? (nint)(object)left < (nint)(object)right + : typeof(T) == typeof(float) ? (float)(object)left < (float)(object)right + : typeof(T) == typeof(double) ? (double)(object)left < (double)(object)right : left.CompareTo(right) < 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] // compiles to a single comparison or method call private static bool GreaterThan(ref T left, ref T right) - => typeof(T) == typeof(byte) - ? (byte)(object)left > (byte)(object)right - : typeof(T) == typeof(sbyte) - ? (sbyte)(object)left > (sbyte)(object)right - : typeof(T) == typeof(ushort) - ? (ushort)(object)left > (ushort)(object)right - : typeof(T) == typeof(short) - ? (short)(object)left > (short)(object)right - : typeof(T) == typeof(uint) - ? (uint)(object)left > (uint)(object)right - : typeof(T) == typeof(int) - ? (int)(object)left > (int)(object)right - : typeof(T) == typeof(ulong) - ? (ulong)(object)left > (ulong)(object)right - : typeof(T) == typeof(long) - ? (long)(object)left > (long)(object)right - : typeof(T) == typeof(nuint) - ? (nuint)(object)left > (nuint)(object)right - : typeof(T) == typeof(nint) - ? (nint)(object)left > (nint)(object)right - : typeof(T) == typeof(float) - ? (float)(object)left > (float)(object)right - : typeof(T) == typeof(double) - ? (double)(object)left > (double)(object)right + => typeof(T) == typeof(byte) ? (byte)(object)left > (byte)(object)right + : typeof(T) == typeof(sbyte) ? (sbyte)(object)left > (sbyte)(object)right + : typeof(T) == typeof(ushort) ? (ushort)(object)left > (ushort)(object)right + : typeof(T) == typeof(short) ? (short)(object)left > (short)(object)right + : typeof(T) == typeof(uint) ? (uint)(object)left > (uint)(object)right + : typeof(T) == typeof(int) ? (int)(object)left > (int)(object)right + : typeof(T) == typeof(ulong) ? (ulong)(object)left > (ulong)(object)right + : typeof(T) == typeof(long) ? (long)(object)left > (long)(object)right + : typeof(T) == typeof(nuint) ? (nuint)(object)left > (nuint)(object)right + : typeof(T) == typeof(nint) ? (nint)(object)left > (nint)(object)right + : typeof(T) == typeof(float) ? (float)(object)left > (float)(object)right + : typeof(T) == typeof(double) ? (double)(object)left > (double)(object)right : left.CompareTo(right) > 0; } @@ -525,8 +489,9 @@ internal sealed class ArraySortHelper : IArraySortHelper CreateArraySortHelper() => typeof(IComparable).IsAssignableFrom(typeof(TKey)) - ? (IArraySortHelper)Activator.CreateInstance(typeof(GenericArraySortHelper<,>).MakeGenericType(typeof(TKey), typeof(TValue))) - : new ArraySortHelper(); + ? (IArraySortHelper)Activator.CreateInstance( + typeof(GenericArraySortHelper<,>).MakeGenericType(typeof(TKey), typeof(TValue))) + : new ArraySortHelper(); public void Sort(Span keys, Span values, IComparer? comparer) { @@ -546,19 +511,20 @@ internal sealed class ArraySortHelper : IArraySortHelper keys, Span values, IComparer comparer, int i, int j) + private static void SwapIfGreaterWithValues(Span keys, Span values, IComparer comparer, int i, + int j) { Debug.Assert(comparer != null); Debug.Assert(0 <= i && i < keys.Length && i < values.Length); Debug.Assert(0 <= j && j < keys.Length && j < values.Length); Debug.Assert(i != j); - if (comparer!.Compare(keys[i], keys[j]) > 0) - { - (keys[j], keys[i]) = (keys[i], keys[j]); + if (comparer!.Compare(keys[i], keys[j]) <= 0) + return; - (values[j], values[i]) = (values[i], values[j]); - } + (keys[j], keys[i]) = (keys[i], keys[j]); + + (values[j], values[i]) = (values[i], values[j]); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -577,9 +543,7 @@ internal sealed class ArraySortHelper : IArraySortHelper 1) - { IntroSort(keys, values, 2 * (BitOperations.Log2((uint)keys.Length) + 1), comparer!); - } } public static void IntroSort(Span keys, Span values, int depthLimit, IComparer comparer) @@ -594,7 +558,6 @@ internal sealed class ArraySortHelper : IArraySortHelper : IArraySortHelper : IArraySortHelper : IArraySortHelper : IArraySortHelper> 1; i >= 1; i--) - { DownHeap(keys, values, i, n, comparer!); - } for (var i = n; i > 1; i--) { @@ -697,9 +659,7 @@ internal sealed class ArraySortHelper : IArraySortHelper : IArraySortHelper : IArraySortHelper.IntrospectiveSort(keys, values, comparer); - } } catch (IndexOutOfRangeException) { @@ -818,7 +774,6 @@ internal sealed class GenericArraySortHelper : IArraySortHelper : IArraySortHelper : IArraySortHelper 0 && keys[--right] != null) ; @@ -895,9 +853,7 @@ internal sealed class GenericArraySortHelper : IArraySortHelper : IArraySortHelper> 1; i >= 1; i--) - { DownHeap(keys, values, i, n); - } for (var i = n; i > 1; i--) { @@ -927,9 +881,7 @@ internal sealed class GenericArraySortHelper : IArraySortHelper : IArraySortHelper typeof(TKey) == typeof(byte) - ? (byte)(object)left < (byte)(object)right - : typeof(TKey) == typeof(sbyte) - ? (sbyte)(object)left < (sbyte)(object)right - : typeof(TKey) == typeof(ushort) - ? (ushort)(object)left < (ushort)(object)right - : typeof(TKey) == typeof(short) - ? (short)(object)left < (short)(object)right - : typeof(TKey) == typeof(uint) - ? (uint)(object)left < (uint)(object)right - : typeof(TKey) == typeof(int) - ? (int)(object)left < (int)(object)right - : typeof(TKey) == typeof(ulong) - ? (ulong)(object)left < (ulong)(object)right - : typeof(TKey) == typeof(long) - ? (long)(object)left < (long)(object)right - : typeof(TKey) == typeof(nuint) - ? (nuint)(object)left < (nuint)(object)right - : typeof(TKey) == typeof(nint) - ? (nint)(object)left < (nint)(object)right - : typeof(TKey) == typeof(float) - ? (float)(object)left < (float)(object)right - : typeof(TKey) == typeof(double) - ? (double)(object)left < (double)(object)right + => typeof(TKey) == typeof(byte) ? (byte)(object)left < (byte)(object)right + : typeof(TKey) == typeof(sbyte) ? (sbyte)(object)left < (sbyte)(object)right + : typeof(TKey) == typeof(ushort) ? (ushort)(object)left < (ushort)(object)right + : typeof(TKey) == typeof(short) ? (short)(object)left < (short)(object)right + : typeof(TKey) == typeof(uint) ? (uint)(object)left < (uint)(object)right + : typeof(TKey) == typeof(int) ? (int)(object)left < (int)(object)right + : typeof(TKey) == typeof(ulong) ? (ulong)(object)left < (ulong)(object)right + : typeof(TKey) == typeof(long) ? (long)(object)left < (long)(object)right + : typeof(TKey) == typeof(nuint) ? (nuint)(object)left < (nuint)(object)right + : typeof(TKey) == typeof(nint) ? (nint)(object)left < (nint)(object)right + : typeof(TKey) == typeof(float) ? (float)(object)left < (float)(object)right + : typeof(TKey) == typeof(double) ? (double)(object)left < (double)(object)right : left.CompareTo(right) < 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] // compiles to a single comparison or method call private static bool GreaterThan(ref TKey left, ref TKey right) - => typeof(TKey) == typeof(byte) - ? (byte)(object)left > (byte)(object)right - : typeof(TKey) == typeof(sbyte) - ? (sbyte)(object)left > (sbyte)(object)right - : typeof(TKey) == typeof(ushort) - ? (ushort)(object)left > (ushort)(object)right - : typeof(TKey) == typeof(short) - ? (short)(object)left > (short)(object)right - : typeof(TKey) == typeof(uint) - ? (uint)(object)left > (uint)(object)right - : typeof(TKey) == typeof(int) - ? (int)(object)left > (int)(object)right - : typeof(TKey) == typeof(ulong) - ? (ulong)(object)left > (ulong)(object)right - : typeof(TKey) == typeof(long) - ? (long)(object)left > (long)(object)right - : typeof(TKey) == typeof(nuint) - ? (nuint)(object)left > (nuint)(object)right - : typeof(TKey) == typeof(nint) - ? (nint)(object)left > (nint)(object)right - : typeof(TKey) == typeof(float) - ? (float)(object)left > (float)(object)right - : typeof(TKey) == typeof(double) - ? (double)(object)left > (double)(object)right + => typeof(TKey) == typeof(byte) ? (byte)(object)left > (byte)(object)right + : typeof(TKey) == typeof(sbyte) ? (sbyte)(object)left > (sbyte)(object)right + : typeof(TKey) == typeof(ushort) ? (ushort)(object)left > (ushort)(object)right + : typeof(TKey) == typeof(short) ? (short)(object)left > (short)(object)right + : typeof(TKey) == typeof(uint) ? (uint)(object)left > (uint)(object)right + : typeof(TKey) == typeof(int) ? (int)(object)left > (int)(object)right + : typeof(TKey) == typeof(ulong) ? (ulong)(object)left > (ulong)(object)right + : typeof(TKey) == typeof(long) ? (long)(object)left > (long)(object)right + : typeof(TKey) == typeof(nuint) ? (nuint)(object)left > (nuint)(object)right + : typeof(TKey) == typeof(nint) ? (nint)(object)left > (nint)(object)right + : typeof(TKey) == typeof(float) ? (float)(object)left > (float)(object)right + : typeof(TKey) == typeof(double) ? (double)(object)left > (double)(object)right : left.CompareTo(right) > 0; } @@ -1041,15 +969,13 @@ internal static class SortUtils for (var i = 0; i < keys.Length; i++) { - if ((typeof(TKey) == typeof(double) && double.IsNaN((double)(object)keys[i])) || - (typeof(TKey) == typeof(float) && float.IsNaN((float)(object)keys[i]))) + if ((typeof(TKey) == typeof(double) && double.IsNaN((double)(object)keys[i])) + || (typeof(TKey) == typeof(float) && float.IsNaN((float)(object)keys[i]))) { (keys[i], keys[left]) = (keys[left], keys[i]); if ((uint)i < (uint)values.Length) // check to see if we have values - { (values[i], values[left]) = (values[left], values[i]); - } left++; } @@ -1067,9 +993,12 @@ internal static class SortUtils internal static void ThrowArgumentException_BadComparer(object? comparer) => throw new ArgumentException(string.Format(Arg_BogusIComparer, comparer)); - internal static string InvalidOperation_IComparerFailed => "Failed to compare two elements in the array."; + internal static string InvalidOperation_IComparerFailed = "Failed to compare two elements in the array."; + internal static string Arg_BogusIComparer - => "Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'."; + = "Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not " + + "compare equal to itself, or one value repeatedly compared to another value yields different results. " + + "IComparer: '{0}'."; } internal interface IArraySortHelper @@ -1085,12 +1014,40 @@ internal interface IArraySortHelper internal static class BitOperations { private static ReadOnlySpan Log2DeBruijn - => new byte[32] + => new byte[] { - 00, 09, 01, 10, 13, 21, 02, 29, - 11, 14, 16, 18, 22, 25, 03, 30, - 08, 12, 20, 28, 15, 17, 24, 07, - 19, 27, 23, 06, 26, 05, 04, 31 + 00, + 09, + 01, + 10, + 13, + 21, + 02, + 29, + 11, + 14, + 16, + 18, + 22, + 25, + 03, + 30, + 08, + 12, + 20, + 28, + 15, + 17, + 24, + 07, + 19, + 27, + 23, + 06, + 26, + 05, + 04, + 31 }; /// @@ -1160,4 +1117,5 @@ internal static class BitOperations // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here (IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); } -} \ No newline at end of file +} +#pragma warning restore CS8500 \ No newline at end of file diff --git a/Source/FisheryLib/BoolExtensions.cs b/Source/FisheryLib/BoolExtensions.cs index 5942908..b25c288 100644 --- a/Source/FisheryLib/BoolExtensions.cs +++ b/Source/FisheryLib/BoolExtensions.cs @@ -5,12 +5,14 @@ // https://github.com/CommunityToolkit/dotnet/blob/main/CommunityToolkit.HighPerformance/Extensions/BoolExtensions.cs using System.Runtime.CompilerServices; +using JetBrains.Annotations; namespace FisheryLib; /// /// Helpers for working with the type. /// +[PublicAPI] public static class BoolExtensions { /// @@ -27,7 +29,7 @@ public static class BoolExtensions // remove unwanted stack spills if the caller is using constant arguments. This is // because taking the address of an argument can interfere with some of the flow // analysis executed by the JIT, which can in some cases block constant propagation. - bool copy = flag; + var copy = flag; return *(byte*)© } @@ -49,10 +51,10 @@ public static class BoolExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int ToBitwiseMask32(this bool flag) { - bool copy = flag; - byte rangeFlag = *(byte*)© - int negativeFlag = rangeFlag - 1; - int mask = ~negativeFlag; + var copy = flag; + var rangeFlag = *(byte*)© + var negativeFlag = rangeFlag - 1; + var mask = ~negativeFlag; return mask; } @@ -67,10 +69,10 @@ public static class BoolExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe long ToBitwiseMask64(this bool flag) { - bool copy = flag; - byte rangeFlag = *(byte*)© - long negativeFlag = (long)rangeFlag - 1; - long mask = ~negativeFlag; + var copy = flag; + var rangeFlag = *(byte*)© + var negativeFlag = (long)rangeFlag - 1; + var mask = ~negativeFlag; return mask; } diff --git a/Source/FisheryLib/Cecil/CecilExtensions.cs b/Source/FisheryLib/Cecil/CecilExtensions.cs new file mode 100644 index 0000000..2b2f4eb --- /dev/null +++ b/Source/FisheryLib/Cecil/CecilExtensions.cs @@ -0,0 +1,559 @@ +// Copyright (c) 2023 bradson +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +using System.Runtime.CompilerServices; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Collections.Generic; +using Verse; +using CallSite = Mono.Cecil.CallSite; + +namespace FisheryLib.Cecil; + +public static class CecilExtensions +{ + public static MethodReference ImportReference(this ModuleDefinition module, Delegate method) + => module.ImportReference(method.Method); + + public static MethodReference MakeGenericMethod(this MethodReference genericMethodDefinition, + ICollection genericArguments) where T : TypeReference + { + if (genericArguments.Count != genericMethodDefinition.GenericParameters.Count) + ThrowForInvalidGenericArgumentCount(genericMethodDefinition, genericArguments); + + var genericInstanceMethod = new GenericInstanceMethod(genericMethodDefinition); + foreach (var genericArgument in genericArguments) + genericInstanceMethod.GenericArguments.Add(genericArgument); + + return genericInstanceMethod; + } + + private static void ThrowForInvalidGenericArgumentCount(IGenericParameterProvider genericParameterProvider, + ICollection genericArguments) where T : TypeReference + => throw new ArgumentException( + $"Invalid generic argument count for {genericParameterProvider}. Received { + genericArguments.Count}, must be {genericParameterProvider.GenericParameters.Count}"); + + public static MethodReference MakeGenericMethod(this MethodReference genericMethodDefinition, + params T[] genericArguments) where T : TypeReference + => MakeGenericMethod(genericMethodDefinition, (ICollection)genericArguments); + + public static MethodReference ImportMethodWithGenericArguments(this ModuleDefinition module, MethodInfo method, + ICollection genericArguments) where T : TypeReference + => module.ImportReference(method.IsGenericMethodDefinition ? method : method.GetGenericMethodDefinition()) + .MakeGenericMethod(genericArguments); + + public static MethodReference ImportMethodWithGenericArguments(this ModuleDefinition module, MethodInfo method, + params T[] genericArguments) where T : TypeReference + => module.ImportMethodWithGenericArguments(method, (ICollection)genericArguments); + + public static MethodReference ImportMethodWithGenericArguments(this ModuleDefinition module, Delegate method, + ICollection genericArguments) where T : TypeReference + => module.ImportMethodWithGenericArguments(method.Method, genericArguments); + + public static MethodReference ImportMethodWithGenericArguments(this ModuleDefinition module, Delegate method, + params T[] genericArguments) where T : TypeReference + => module.ImportMethodWithGenericArguments(method.Method, (ICollection)genericArguments); + + public static TypeReference MakeGenericType(this TypeReference genericTypeDefinition, + ICollection genericArguments) where T : TypeReference + { + if (genericArguments.Count != genericTypeDefinition.GenericParameters.Count) + ThrowForInvalidGenericArgumentCount(genericTypeDefinition, genericArguments); + + var genericInstanceType = new GenericInstanceType(genericTypeDefinition); + foreach (var genericArgument in genericArguments) + genericInstanceType.GenericArguments.Add(genericArgument); + + return genericInstanceType; + } + + public static TypeReference MakeGenericType(this TypeReference genericTypeDefinition, + params T[] genericArguments) where T : TypeReference + => MakeGenericType(genericTypeDefinition, (ICollection)genericArguments); + + public static TypeReference ImportTypeWithGenericArguments(this ModuleDefinition module, Type type, + ICollection genericArguments) where T : TypeReference + => module.ImportReference(type.IsGenericTypeDefinition ? type : type.GetElementType()) + .MakeGenericType(genericArguments); + + public static TypeReference ImportTypeWithGenericArguments(this ModuleDefinition module, Type type, + params T[] genericArguments) where T : TypeReference + => module.ImportTypeWithGenericArguments(type, (ICollection)genericArguments); + + public static VariableDefinition DeclareLocal(this ILProcessor ilProcessor, Type localType) + { + var variableDefinition = new VariableDefinition(ilProcessor.GetModule().ImportReference(localType)); + ilProcessor.Body.Variables.Add(variableDefinition); + return variableDefinition; + } + + public static VariableDefinition DeclareLocal(this ILProcessor ilProcessor, TypeReference localType) + { + var variableDefinition = new VariableDefinition(localType); + ilProcessor.Body.Variables.Add(variableDefinition); + return variableDefinition; + } + + public static void InsertAt(this ILProcessor ilProcessor, int index, Instruction instruction) + => ilProcessor.instructions.Insert(index, instruction); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode) + => ilProcessor.InsertAt(index, Instruction.Create(opCode)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, TypeReference type) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, type)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, Type type) + => ilProcessor.InsertAt(index, opCode, ilProcessor.GetModule().ImportReference(type)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, CallSite site) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, site)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, MethodReference method) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, method)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, MethodBase method, + params Type[] genericArguments) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, + ImportReferenceWithOptionalGenericArguments(method, ilProcessor.GetModule(), genericArguments))); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, Delegate method, + params Type[] genericArguments) + => ilProcessor.InsertAt(index, opCode, method.Method, genericArguments); + + private static MethodReference ImportReferenceWithOptionalGenericArguments(MethodBase method, + ModuleDefinition module, params Type[] genericArguments) + => genericArguments.Length > 0 + ? module.ImportMethodWithGenericArguments((MethodInfo)method, genericArguments) + : module.ImportReference(method); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, FieldReference field) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, field)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, FieldInfo field) + => ilProcessor.InsertAt(index, opCode, ilProcessor.GetModule().ImportReference(field)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, string value) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, value)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, sbyte value) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, value)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, byte value) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, value)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, int value) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, value)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, long value) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, value)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, nint value) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, value)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, float value) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, value)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, double value) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, value)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, Instruction target) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, target)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, Instruction[] targets) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, targets)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, VariableDefinition variable) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, variable)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, ParameterDefinition parameter) + => ilProcessor.InsertAt(index, Instruction.Create(opCode, parameter)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, object operand) + => ilProcessor.InsertAt(index, CreateInstruction(opCode, operand)); + + public static void InsertAt(this ILProcessor ilProcessor, int index, object instruction) + { + var module = ilProcessor.GetModule(); + ilProcessor.InsertAt(index, instruction switch + { + Instruction codeInstruction => codeInstruction, + OpCode opCode => Instruction.Create(opCode), + ITuple tuple => CreateInstruction(tuple is [OpCode opCode, _, ..] + ? opCode + : ThrowArgumentExceptionForInvalidType(instruction), + tuple[1] switch + { + MethodBase => ImportMethodFromTuple(tuple, module), + FieldInfo fieldInfo => module.ImportReference(fieldInfo), + Type => ImportTypeFromTuple(tuple, module), + ParameterInfo parameterInfo => GetParameterDefinition(ilProcessor, parameterInfo), + Delegate @delegate => module.ImportReference(@delegate.Method), + _ => tuple[1] + }), + _ => ThrowArgumentExceptionForInvalidType(instruction) + }); + } + + private static ParameterDefinition GetParameterDefinition(ILProcessor ilProcessor, ParameterInfo parameterInfo) + { + var parameters = ilProcessor.Body.Method.Parameters; + foreach (var parameter in parameters) + { + if (parameter.Name == parameterInfo.Name) + return parameter; + } + + return ThrowHelper.ThrowArgumentException($"Could not find parameter named { + parameterInfo.Name} in method {ilProcessor.Body.Method.FullName}"); + } + + private static MethodReference ImportMethodFromTuple(ITuple tuple, ModuleDefinition module) + { + var tupleLength = tuple.Length; + Guard.IsGreaterThanOrEqualTo(tupleLength, 2); + + if (tupleLength == 2) + return module.ImportReference((MethodBase)tuple[1]); + + var methodInfo = (MethodInfo)tuple[1]; + switch (tuple[2]) + { + case Type[] typeArray: + { + return module.ImportMethodWithGenericArguments(methodInfo, typeArray); + } + case Type: + { + var genericArguments = new Type[tupleLength - 2]; + for (var i = 2; i < tupleLength; i++) + genericArguments[i - 2] = (Type)tuple[i]; + + return module.ImportMethodWithGenericArguments(methodInfo, genericArguments); + } + case Collection genericParameterCollection: + { + return module.ImportMethodWithGenericArguments(methodInfo, genericParameterCollection); + } + case Collection typeReferenceCollection: + { + return module.ImportMethodWithGenericArguments(methodInfo, typeReferenceCollection); + } + case TypeReference: + { + var genericArguments = new TypeReference[tupleLength - 2]; + for (var i = 2; i < tupleLength; i++) + genericArguments[i - 2] = (TypeReference)tuple[i]; + + return module.ImportMethodWithGenericArguments(methodInfo, genericArguments); + } + default: + { + return ThrowHelper.ThrowArgumentException( + $"Tuple contains invalid type: {tuple[2].GetType().FullName}"); + } + } + } + + private static TypeReference ImportTypeFromTuple(ITuple tuple, ModuleDefinition module) + { + var tupleLength = tuple.Length; + Guard.IsGreaterThanOrEqualTo(tupleLength, 2); + + if (tupleLength == 2) + return module.ImportReference((Type)tuple[1]); + + var type = (Type)tuple[1]; + switch (tuple[2]) + { + case Type[] typeArray: + { + return module.ImportTypeWithGenericArguments(type, typeArray); + } + case Type: + { + var genericArguments = new Type[tupleLength - 2]; + for (var i = 2; i < tupleLength; i++) + genericArguments[i - 2] = (Type)tuple[i]; + + return module.ImportTypeWithGenericArguments(type, genericArguments); + } + case Collection genericParameterCollection: + { + return module.ImportTypeWithGenericArguments(type, genericParameterCollection); + } + case Collection typeReferenceCollection: + { + return module.ImportTypeWithGenericArguments(type, typeReferenceCollection); + } + case TypeReference: + { + var genericArguments = new TypeReference[tupleLength - 2]; + for (var i = 2; i < tupleLength; i++) + genericArguments[i - 2] = (TypeReference)tuple[i]; + + return module.ImportTypeWithGenericArguments(type, genericArguments); + } + default: + { + return ThrowHelper.ThrowArgumentException( + $"Tuple contains invalid type: {tuple[2].GetType().FullName}"); + } + } + } + + public static void InsertRange(this ILProcessor ilProcessor, int index, IEnumerable instructions) + { + foreach (var instruction in instructions) + ilProcessor.InsertAt(index++, instruction); + } + + public static void InsertRange(this ILProcessor ilProcessor, int index, params Instruction[] instructions) + { + for (var i = 0; i < instructions.Length; i++) + ilProcessor.InsertAt(index++, instructions[i]); + } + + public static void InsertRange(this ILProcessor ilProcessor, int index, IEnumerable instructions) + { + foreach (var instruction in instructions) + ilProcessor.InsertAt(index++, instruction); + } + + public static void InsertRange(this ILProcessor ilProcessor, int index, params object[] instructions) + { + for (var i = 0; i < instructions.Length; i++) + ilProcessor.InsertAt(index++, instructions[i]); + } + + public static void ReplaceBodyWith(this ILProcessor ilProcessor, Delegate replacement) + => ilProcessor.ReplaceBodyWith(replacement.Method); + + public static void ReplaceBodyWith(this ILProcessor ilProcessor, MethodInfo replacement) + { + var oldBody = ilProcessor.Body; + var module = oldBody.Method.Module; + + if (replacement.IsGenericMethod) + replacement = replacement.GetGenericMethodDefinition(); + + if (replacement.DeclaringType!.IsGenericType) + { + replacement = AccessTools.DeclaredMethod(replacement.DeclaringType.GetGenericTypeDefinition(), + replacement.Name, replacement.GetParameters().GetTypes()); + } + + var newMethodReference = module.ImportReference(replacement); + var newBody = newMethodReference.Resolve().Body; + + oldBody.Instructions.Clear(); + foreach (var instruction in newBody.instructions) + { + oldBody.Instructions.Add(new(instruction.OpCode, instruction.Operand switch + { + FieldReference field => module.ImportReference(field, newMethodReference), + MethodReference method => module.ImportReference(method, newMethodReference), + TypeReference type => module.ImportReference(type, newMethodReference), + _ => instruction.Operand + })); + } + + oldBody.Variables.Clear(); + foreach (var variable in newBody.Variables) + oldBody.Variables.Add(new(module.ImportReference(variable.VariableType, newMethodReference))); + + oldBody.MaxStackSize = newBody.MaxStackSize; + + oldBody.ExceptionHandlers.Clear(); + foreach (var exceptionHandler in newBody.ExceptionHandlers) + { + var handler = new ExceptionHandler(exceptionHandler.HandlerType) + { + TryStart = exceptionHandler.TryStart, + TryEnd = exceptionHandler.TryEnd, + FilterStart = exceptionHandler.FilterStart, + HandlerStart = exceptionHandler.HandlerStart, + HandlerEnd = exceptionHandler.HandlerEnd + }; + + if (exceptionHandler.CatchType is { } catchType) + handler.CatchType = module.ImportReference(catchType, newMethodReference); + + oldBody.ExceptionHandlers.Add(handler); + } + + oldBody.Method.ImplAttributes = newBody.Method.ImplAttributes; + } + + public static MethodDefinition GetMethod(this ILProcessor ilProcessor) => ilProcessor.Body.Method; + + public static MethodDefinition? GetMethod(this TypeDefinition type, string name, Type?[]? parameters = null) + { + var methods = type.Methods; + MethodDefinition? foundMethod = null; + + for (var i = 0; i < methods.Count; i++) + { + var method = methods[i]; + if (method.Name != name + || !TestForMatchingTypes(parameters, method.Parameters, static p => p.ParameterType.Name)) + { + continue; + } + + if (foundMethod != null) + ThrowForAmbiguousMatch(type, name, parameters); + else + foundMethod = method; + } + + return foundMethod; + } + + public static TypeDefinition? GetTypeDefinition(this ModuleDefinition module, Type type) + => type.DeclaringType is { } declaringType + ? module.GetTypeDefinition(declaringType)?.NestedTypes.GetNamed(type.Name) + : module.GetType(type.Namespace, type.Name); + + private static void ThrowForAmbiguousMatch(TypeDefinition type, string name, Type?[]? parameters) + => throw new AmbiguousMatchException($"Ambiguous match found for type: {type.FullName}, name: { + name}, parameters: {parameters.ToStringSafeEnumerable()}"); + + private static bool TestForMatchingTypes(Type?[]? types, Collection? testedTypes, + Func typeNameGetter) + { + if (types is null) + return true; + + if (testedTypes is null || testedTypes.Count != types.Length) + return false; + + var typesNotMatching = false; + for (var i = 0; i < types.Length; i++) + { + var type = types[i]; + if (type is null || type.IsGenericParameter) + continue; + + if (typeNameGetter(testedTypes[i]) != type.Name) + typesNotMatching = true; + } + + return !typesNotMatching; + } + + public static ModuleDefinition GetModule(this ILProcessor ilProcessor) => ilProcessor.GetMethod().Module; + + public static Collection? TryGetGenericArguments(this MethodReference method) + => method is GenericInstanceMethod instanceMethod + ? instanceMethod.GenericArguments + : method.DeclaringType.TryGetGenericArguments(); + + public static Collection? TryGetGenericArguments(this TypeReference type) + { + while (type != null) + { + if (type is GenericInstanceType instanceType) + return instanceType.GenericArguments; + + type = type.DeclaringType; + } + + return null; + } + + public static Collection? TryGetGenericArguments(this TypeReference type, + IGenericParameterProvider provider) + { + while (type != null) + { + if (type is GenericInstanceType instanceType) + return instanceType.GenericArguments.ConvertAll(g => g.TryGetGenericParameterType(provider) ?? g); + + type = type.DeclaringType; + } + + return null; + } + + public static Collection? TryGetGenericArguments(this IGenericParameterProvider provider) + => provider switch + { + null => ThrowHelper.ThrowArgumentNullException>(nameof(provider)), + MethodReference method => method.TryGetGenericArguments(), + TypeReference type => type.TryGetGenericArguments(), + _ => ThrowHelper.ThrowArgumentException>( + $"Generic parameter provider has unsupported type: {provider.GetType()}") + }; + + public static TypeReference? TryGetGenericParameterType(this TypeReference typeReference, + IGenericParameterProvider provider) + => typeReference is GenericParameter genericParameter + ? TryGetUnderlyingType(genericParameter, provider) + : null; + + public static TypeReference? TryGetUnderlyingType(GenericParameter genericParameter, + IGenericParameterProvider provider) + => (genericParameter.Owner is TypeReference ownerType && provider is MemberReference providerMember + ? providerMember.TryGetParentOfName(ownerType.Name) + : provider)?.TryGetGenericArguments()?[genericParameter.Position]; + + + public static Collection ConvertAll(this Collection collection, + Converter converter) + { + var result = new Collection(collection.Count); + + for (var i = 0; i < collection.Count; i++) + result.Add(converter(collection[i])); // somehow throws OutOfRangeExceptions when using set_Item here + // Storing collection.Count in a var makes no difference + + return result; + } + + private static TypeReference? TryGetParentOfName(this MemberReference providerMember, string name) + { + var type = providerMember as TypeReference ?? providerMember.DeclaringType; + while (type != null) + { + if (type.Name == name) + return type; + + type = type.DeclaringType; + } + + return null; + } + + private static T ThrowArgumentExceptionForInvalidType(object instruction) + => ThrowHelper.ThrowArgumentException(nameof(instruction), + // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract + $"Argument is of invalid type: {instruction?.GetType().FullName ?? "NULL"}"); + + private static Instruction CreateInstruction(OpCode opCode, object operand) + => operand switch + { + TypeReference type => Instruction.Create(opCode, type), + CallSite site => Instruction.Create(opCode, site), + MethodReference method => Instruction.Create(opCode, method), + FieldReference field => Instruction.Create(opCode, field), + string value => Instruction.Create(opCode, value), + sbyte value => Instruction.Create(opCode, value), + byte value => Instruction.Create(opCode, value), + int value => Instruction.Create(opCode, value), + long value => Instruction.Create(opCode, value), + float value => Instruction.Create(opCode, value), + double value => Instruction.Create(opCode, value), + nint value => Instruction.Create(opCode, value), + Instruction target => Instruction.Create(opCode, target), + Instruction[] targets => Instruction.Create(opCode, targets), + VariableDefinition variable => Instruction.Create(opCode, variable), + ParameterDefinition parameter => Instruction.Create(opCode, parameter), + _ => ThrowHelper.ThrowArgumentException(nameof(operand), + // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract + $"Operand for OpCode {opCode} is of invalid type: {operand?.GetType().FullName ?? "NULL"}") + }; +} \ No newline at end of file diff --git a/Source/FisheryLib/Cecil/CecilReflectionExtensions.cs b/Source/FisheryLib/Cecil/CecilReflectionExtensions.cs new file mode 100644 index 0000000..6171a9c --- /dev/null +++ b/Source/FisheryLib/Cecil/CecilReflectionExtensions.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2022 bradson +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v.2.0.If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +using Mono.Cecil; + +namespace FisheryLib.Cecil; + +public static class CecilReflectionExtensions +{ + public static MethodReference ImportMethodWithGenericArguments(this ModuleDefinition module, MethodInfo method, + params Type[] genericArguments) + => module.ImportReference(method.IsGenericMethodDefinition ? method : method.GetGenericMethodDefinition()) + .MakeGenericMethod(genericArguments.GetTypeReferences(module)); + + public static MethodReference ImportMethodWithGenericArguments(this ModuleDefinition module, Delegate method, + params Type[] genericArguments) + => module.ImportMethodWithGenericArguments(method.Method, genericArguments); + + public static TypeReference ImportTypeWithGenericArguments(this ModuleDefinition module, Type type, + params Type[] genericArguments) + => module.ImportReference(type.IsGenericTypeDefinition ? type : type.GetElementType()) + .MakeGenericType(genericArguments.GetTypeReferences(module)); +} \ No newline at end of file diff --git a/Source/FisheryLib/Cecil/ReflectionExtensions.cs b/Source/FisheryLib/Cecil/ReflectionExtensions.cs new file mode 100644 index 0000000..ab83ad7 --- /dev/null +++ b/Source/FisheryLib/Cecil/ReflectionExtensions.cs @@ -0,0 +1,84 @@ +// Copyright (c) 2023 bradson +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; +using Mono.Cecil; +using Mono.Collections.Generic; + +namespace FisheryLib.Cecil; + +[PublicAPI] +public static class ReflectionExtensions +{ + public static Type[] GetTypes(this ParameterInfo[] parameterInfos) + => Array.ConvertAll(parameterInfos, static p => p.ParameterType); + + public static ParameterInfo? TryGetNamed(this ParameterInfo[] parameterInfos, string name) + => TryGetNamed(parameterInfos, name, static p => p.Name); + + public static T? TryGetNamed(this T[] memberInfos, string name) where T : MemberInfo + => TryGetNamed(memberInfos, name, static m => m.Name); + + public static ParameterDefinition? TryGetNamed(this Collection parameterDefinitions, + string name) + => TryGetNamed(parameterDefinitions, name, static p => p.Name); + + public static T? TryGetNamed(this Collection memberReferences, string name) where T : MemberReference + => TryGetNamed(memberReferences, name, static m => m.Name); + + public static ParameterInfo GetNamed(this ParameterInfo[] parameterInfos, string name) + => TryGetNamed(parameterInfos, name, static p => p.Name) + ?? ThrowForMissingParameter(name); + + public static T GetNamed(this T[] memberInfos, string name) where T : MemberInfo + => TryGetNamed(memberInfos, name, static m => m.Name) + ?? ThrowForMissingMember(name); + + public static ParameterDefinition GetNamed(this Collection parameterDefinitions, + string name) + => TryGetNamed(parameterDefinitions, name, static p => p.Name) + ?? ThrowForMissingParameter(name); + + public static T GetNamed(this Collection memberReferences, string name) where T : MemberReference + => TryGetNamed(memberReferences, name, static m => m.Name) + ?? ThrowForMissingMember(name); + + [DoesNotReturn] + private static T ThrowForMissingMember(string name) + => ThrowHelper.ThrowInvalidOperationException($"Sequence contains no member with name: {name}"); + + [DoesNotReturn] + private static T ThrowForMissingParameter(string name) + => ThrowHelper.ThrowInvalidOperationException( + $"Sequence contains no parameter with name: {name}"); + + private static T? TryGetNamed(this IList memberInfos, string name, Func nameGetter) + where T : class + { + for (var i = 0; i < memberInfos.Count; i++) + { + if (nameGetter(memberInfos[i]) == name) + return memberInfos[i]; + } + + return null; + } + + public static TypeReference[] GetTypeReferences(this Type[] types, ModuleDefinition module) + { + var result = new TypeReference[types.Length]; + for (var i = types.Length; i-- > 0;) + result[i] = module.ImportReference(types[i]); + + return result; + } + + public static bool IsGeneric(this TypeReference type) + => type.ContainsGenericParameter || type.HasGenericParameters || type.IsGenericInstance; + + public static bool IsGeneric(this Type type) + => type.ContainsGenericParameters || type.IsGenericType || type.IsConstructedGenericType; +} \ No newline at end of file diff --git a/Source/FisheryLib/CollectionExtensions.cs b/Source/FisheryLib/CollectionExtensions.cs index 136f7de..c612a65 100644 --- a/Source/FisheryLib/CollectionExtensions.cs +++ b/Source/FisheryLib/CollectionExtensions.cs @@ -7,15 +7,20 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; +using FisheryLib.Collections; +using JetBrains.Annotations; namespace FisheryLib; + +[PublicAPI] public static class CollectionExtensions { public static bool TryGetItem(this List list, int index, [NotNullWhen(true)] out T? item) { - if (list.Count > index) + if (list._size > index) { - item = list[index]!; + item = list._items[index]!; return true; } @@ -24,19 +29,23 @@ public static class CollectionExtensions } /// - /// Taken from Span<>.this[] and slower than List<>.this[] due to the ArrayAdjustment + /// Taken from Span.this[] and slower than List.this[] due to the ArrayAdjustment /// internal static T GetItemUnchecked(this List list, int index) - => Unsafe.Add(ref Unsafe.AddByteOffset(ref Unsafe.As>(list._items).Data, PerTypeValues.ArrayAdjustment), index)!; + => Unsafe.Add( + ref Unsafe.AddByteOffset(ref Unsafe.As>(list._items).Data, PerTypeValues.ArrayAdjustment), + index)!; [StructLayout(LayoutKind.Sequential)] private sealed class Pinnable { public T? Data; } + private static class PerTypeValues { public static readonly IntPtr ArrayAdjustment = MeasureArrayAdjustment(); + private static IntPtr MeasureArrayAdjustment() { var array = new T[1]; @@ -60,8 +69,8 @@ public static class CollectionExtensions { var array = new T[length]; - if (enumerable is IList ilist) - array.Fill(ilist); + if (enumerable is IList iList) + array.Fill(iList); else array.FillUsingEnumerable(enumerable); @@ -81,15 +90,16 @@ public static class CollectionExtensions { Guard.IsNotNull(destination); - if (enumerable is IList ilist) - destination.Fill(ilist); + if (enumerable is IList iList) + destination.Fill(iList); else destination.FillUsingEnumerable(enumerable); return destination; } - private static void FillUsingEnumerable(this T[] array, IEnumerable enumerable, int startIndex = 0, int count = -1) + private static void FillUsingEnumerable(this T[] array, IEnumerable enumerable, int startIndex = 0, + int count = -1) { var i = 0u; foreach (var item in enumerable) @@ -102,7 +112,8 @@ public static class CollectionExtensions } } - public static void Fill(this Telement[] array, Tcollection collection, int startIndex = 0, int count = -1) + public static void Fill(this Telement[] array, Tcollection collection, int startIndex = 0, + int count = -1) where Tcollection : IList { Guard.IsNotNull(collection); @@ -124,8 +135,8 @@ public static class CollectionExtensions { Guard.IsNotNull(enumerable); - if (enumerable is IList ilist) - array.Fill(ilist, startIndex, count); + if (enumerable is IList iList) + array.Fill(iList, startIndex, count); else array.FillUsingEnumerable(enumerable, startIndex, count); } @@ -146,6 +157,27 @@ public static class CollectionExtensions bucket = entry.next; } } + + return ref ThrowKeyNotFoundException(key); + } + + public static ref TValue GetReference(this Dictionary dictionary, ref TKey key) + { + if (dictionary.buckets != null) + { + var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF; + + var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length]; + while (bucket >= 0) + { + ref var entry = ref dictionary.entries[bucket]; + if (entry.hashCode == keyCode && Equality.EqualsByRef(ref entry.key, ref key)) + return ref entry.value; + + bucket = entry.next; + } + } + return ref ThrowKeyNotFoundException(key); } @@ -153,6 +185,7 @@ public static class CollectionExtensions /// Returns a reference to the value field of an entry if the key exists and an Unsafe.NullRef{TValue} if not. /// Must be checked with Unsafe.IsNullRef before assigning to a regular var or field without ref. /// + /// The instance /// The key /// ref TValue or Unsafe.NullRef{TValue} public static ref TValue TryGetReferenceUnsafe(this Dictionary dictionary, TKey key) @@ -171,11 +204,11 @@ public static class CollectionExtensions bucket = entry.next; } } + return ref Unsafe.NullRef(); } public static ref TValue TryGetReferenceUnsafe(this Dictionary dictionary, ref TKey key) - where TKey : struct { if (dictionary.buckets != null) { @@ -185,12 +218,13 @@ public static class CollectionExtensions while (bucket >= 0) { ref var entry = ref dictionary.entries[bucket]; - if (entry.hashCode == keyCode && entry.key.Equals(ref key)) + if (entry.hashCode == keyCode && Equality.EqualsByRef(ref entry.key, ref key)) return ref entry.value; bucket = entry.next; } } + return ref Unsafe.NullRef(); } @@ -210,6 +244,7 @@ public static class CollectionExtensions bucket = entry.next; } } + return default; } @@ -230,8 +265,161 @@ public static class CollectionExtensions bucket = entry.next; } } + return default; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref TValue GetOrAddReference(this Dictionary dictionary, TKey key) + where TValue : new() + { + var keyCode = HashCode.Get(key) & 0x7FFFFFFF; + + StartOfLookup: + if (dictionary.buckets != null) + { + var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length]; + while (bucket >= 0) + { + ref var entry = ref dictionary.entries[bucket]; + if (entry.hashCode == keyCode && entry.key.Equals(key)) + return ref entry.value; + + bucket = entry.next; + } + } + + dictionary.Add(key, Reflection.New()); + goto StartOfLookup; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref TValue GetOrAddReference(this Dictionary dictionary, ref TKey key) + where TValue : new() + { + var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF; + + StartOfLookup: + if (dictionary.buckets != null) + { + var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length]; + while (bucket >= 0) + { + ref var entry = ref dictionary.entries[bucket]; + if (entry.hashCode == keyCode && Equality.EqualsByRef(ref entry.key, ref key)) + return ref entry.value; + + bucket = entry.next; + } + } + + dictionary.Add(key, Reflection.New()); + goto StartOfLookup; + } + + // public static bool TryGetOrAddReference(this Dictionary dictionary, TKey key, + // ref TValue reference) + // { + // var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF; + // var result = true; + // + // StartOfLookup: + // if (dictionary.buckets != null) + // { + // var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length]; + // while (bucket >= 0) + // { + // ref var entry = ref dictionary.entries[bucket]; + // if (entry.hashCode == keyCode && entry.key.Equals(key)) + // { + // reference.Value = ref entry.value; + // return result; + // } + // + // bucket = entry.next; + // } + // } + // + // result = false; + // dictionary.Add(key, Reflection.New()); + // goto StartOfLookup; + // } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TValue GetOrAdd(this Dictionary dictionary, TKey key) + where TValue : new() + { + var keyCode = HashCode.Get(key) & 0x7FFFFFFF; + + StartOfLookup: + if (dictionary.buckets != null) + { + var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length]; + while (bucket >= 0) + { + ref var entry = ref dictionary.entries[bucket]; + if (entry.hashCode == keyCode && entry.key.Equals(key)) + return entry.value; + + bucket = entry.next; + } + } + + dictionary.Add(key, Reflection.New()); + goto StartOfLookup; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TValue GetOrAdd(this Dictionary dictionary, ref TKey key) + where TValue : new() + { + var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF; + + StartOfLookup: + if (dictionary.buckets != null) + { + var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length]; + while (bucket >= 0) + { + ref var entry = ref dictionary.entries[bucket]; + if (entry.hashCode == keyCode && Equality.EqualsByRef(ref entry.key, ref key)) + return entry.value; + + bucket = entry.next; + } + } + + dictionary.Add(key, Reflection.New()); + goto StartOfLookup; + } + + public static bool TryGetOrAddValue(this Dictionary dictionary, TKey key, + out TValue value) + { + var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF; + var result = true; + + StartOfLookup: + if (dictionary.buckets != null) + { + var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length]; + while (bucket >= 0) + { + ref var entry = ref dictionary.entries[bucket]; + if (entry.hashCode == keyCode && entry.key.Equals(key)) + { + value = entry.value; + return result; + } + + bucket = entry.next; + } + } + + result = false; + dictionary.Add(key, Reflection.New()); + goto StartOfLookup; + } [DoesNotReturn] private static ref V ThrowKeyNotFoundException(T key) @@ -243,42 +431,106 @@ public static class CollectionExtensions [DoesNotReturn] private static void ThrowKeyNotFoundException(object key) - => throw new KeyNotFoundException( - string.Format("The given key '{0}' was not present in the dictionary.", key)); + => throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary."); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref int Version(this List list) => ref list._version; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AddRangeFast(this List list, List range) + { + if (range._size < 1) + return; + + list.EnsureCapacity(list._size + range._size); + + range.UnsafeCopyTo(list, list._size); + + list._size += range._size; + list._version++; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeCopyTo(this List source, List destination, int destinationStartIndex) + => UnsafeBlockCopy(ref source._items[0], ref destination._items[destinationStartIndex], source._size); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UnsafeCopyTo(this T[] source, T[] destination, int destinationStartIndex) + => UnsafeBlockCopy(ref source[0], ref destination[destinationStartIndex], source.Length); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void UnsafeBlockCopy(ref T source, ref T destination, int count) + => Unsafe.CopyBlock(ref Unsafe.As(ref destination), + ref Unsafe.As(ref source), + (uint)((nint)sizeof(T) * count)); /// - /// Returns a list's internal _items array. Keep in mind list.Version() needs to change every time items are modified. + /// Returns a list's internal _items array. Keep in mind list.Version() needs to change every time items are + /// modified. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T[] ItemsUnchecked(this List list) => list._items; /// - /// Returns a span pointing to a list's internal _items array. Keep in mind list.Version() needs to change every time items are modified. + /// Returns a span pointing to a list's internal _items array. Keep in mind list.Version() needs to change every + /// time items are modified. /// - public static Span AsSpanUnchecked(this List list) => new(list._items, 0, list.Count); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsSpanUnchecked(this List list) => new(list._items, 0, list._size); - public static ReadOnlySpan AsReadOnlySpan(this List list) => new(list._items, 0, list.Count); - - public static void ReplaceContentsWith(this List list, V collection) where V : ICollection + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsReadOnlySpan(this List list) => new(list._items, 0, list._size); + + public static void ReplaceContentsWith(this List list, [AllowNull] List collection) { if (ReferenceEquals(list, collection)) return; if (collection is null - || (collection.Count is var collectionCount - && collectionCount == 0)) + || (collection._size is var collectionCount + && collectionCount == 0)) { list.Clear(); return; } - if (collectionCount <= list.Count) + if (collectionCount <= list._size) { - if (collectionCount < list.Count) - list.RemoveRange(collectionCount, list.Count - collectionCount); + if (collectionCount < list._size) + list.RemoveRange(collectionCount, list._size - collectionCount); + + collection.UnsafeCopyTo(list, 0); + } + else + { + list._items = new T[collectionCount]; + collection.UnsafeCopyTo(list, 0); + list._size = collectionCount; + } + + list._version++; + } + + public static void ReplaceContentsWith(this List list, [AllowNull] V collection) where V : ICollection + { + if (typeof(V) == typeof(List)) + list.ReplaceContentsWith(Unsafe.As>(collection)); + + if (ReferenceEquals(list, collection)) + return; + + if (collection is null + || (collection.Count is var collectionCount + && collectionCount == 0)) + { + list.Clear(); + return; + } + + if (collectionCount <= list._size) + { + if (collectionCount < list._size) + list.RemoveRange(collectionCount, list._size - collectionCount); collection.CopyTo(list._items, 0); } else @@ -287,18 +539,176 @@ public static class CollectionExtensions collection.CopyTo(list._items, 0); list._size = collectionCount; } + list._version++; } + public static FishTable ToFishTable(this IEnumerable source, + Func keySelector) + => source.ToFishTable(keySelector, static item => item); + + public static FishTable ToFishTable(this IEnumerable source, + Func keySelector, Func valueSelector) + { + Guard.IsNotNull(source); + Guard.IsNotNull(keySelector); + Guard.IsNotNull(valueSelector); + + var capacity = 0; + if (source is ICollection iCollection) + capacity = (int)(iCollection.Count * 1.5f); + + var fishTable = new FishTable(capacity); + fishTable.AddRange(source, keySelector, valueSelector); + return fishTable; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Clear(this T[] array) => Array.Clear(array, 0, array.Length); + + // public static List Copy(this List source) + // { + // Guard.IsNotNull(source); + // + // var destination = new List(source._size); + // + // if (source._size == 0) + // return destination; + // + // // Unsafe.CopyBlock(ref Unsafe.As(ref destination._items[0]), + // // ref Unsafe.As(ref source._items[0]), (uint)sizeof(T) * (uint)source._size); + // + // FisheryLib.CollectionExtensions.UnsafeBlockCopy(ref source._items[0], // does a type check that causes issues + // ref destination._items[0], source._size); // when called from transpiled methods + // + // destination._size = source._size; + // + // return destination; + // } + + public static unsafe List Copy(this List source) + { + Guard.IsNotNull(source); + + var destination = new List(source._size); + + if (source._size == 0) + return destination; + + fixed (void* destinationPointer = &destination._items[0], sourcePointer = &source._items[0]) + Unsafe.CopyBlock(destinationPointer, sourcePointer, (uint)(source._size * sizeof(T))); + + destination._size = source._size; + + return destination; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AddRangeFast(this List list, T[] range) + { + Guard.IsNotNull(list); + Guard.IsNotNull(range); + + if (range.Length < 1) + return; + + list.EnsureCapacity(list._size + range.Length); + range.UnsafeCopyTo(list._items, list._size); + + list._size += range.Length; + list._version++; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Add(this List list, string value) + { + Guard.IsNotNull(list); + Guard.IsNotNull(value); + + if (value.Length < 1) + return; + + list.EnsureCapacity(list._size + value.Length); + UnsafeBlockCopy(ref value.m_firstChar, + ref list._items[list._size], value.Length); + + list._size += value.Length; + list._version++; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static StringBuilder Append(this StringBuilder builder, List value) + => builder.Append(value._items, 0, value._size); + + public static ref T GetReferenceUnverifiable(this List list, int index) => ref list._items[index]; + public static IList TryNew(IEnumerable enumerable, Type type) => GetListExtensionMethods(type).New(enumerable); - public static void TryAddRange(this IList list, T collection, Type type) where T : ICollection => GetListExtensionMethods(type).AddRange(list, collection); + public static void TryAddRange(this IList list, T collection, Type type) where T : ICollection + => GetListExtensionMethods(type).AddRange(list, collection); public static Array TryToArray(this IList list, Type type) => GetListExtensionMethods(type).ToArray(list); + + public static unsafe uint GetSizeEstimate(this Dictionary dictionary) + => dictionary.GetType().ComputeManagedObjectSizeEstimate() + + ((uint)sizeof(Dictionary.Entry) * (uint)(dictionary.entries?.Length ?? 0)) + + (sizeof(int) * (uint)(dictionary.buckets?.Length ?? 0)); + + internal static unsafe uint ComputeManagedObjectSizeEstimate(this Type type) + { + var objectHeader = (uint)sizeof(void*) * 2u; + ref var cachedSize = ref CachedManagedObjectSizes.GetOrAddReference(type.TypeHandle.Value); + + if (cachedSize.Size == uint.MaxValue) + UpdateCachedManagedObjectSize(type, ref cachedSize); + + return objectHeader + cachedSize.Size; + } + + private static void UpdateCachedManagedObjectSize(Type type, ref ManagedObjectSize cachedSize) + { + cachedSize.Size = 0; + var fields = type.GetFields(BindingFlags.Instance + | BindingFlags.Public + | BindingFlags.NonPublic); + + for (var i = fields.Length; i-- > 0;) + { + var fieldType = fields[i].FieldType; + if (!typeof(ValueType).IsAssignableFrom(fieldType) || fieldType == typeof(ValueType)) + // IsValueType and IsSubclassOf somehow cause StackOverflows here + cachedSize.Size += (uint)IntPtr.Size; + else if (fieldType.IsPrimitive) + cachedSize.Size += (uint)Marshal.SizeOf(fieldType); + else if (fieldType != type) + cachedSize.Size += fieldType.ComputeManagedObjectSizeEstimate(); + else + cachedSize.Size += (uint)IntPtr.Size; // fuck it + } + + var remainder = cachedSize.Size % (uint)IntPtr.Size; + if (remainder != 0u) + cachedSize.Size += (uint)IntPtr.Size - remainder; + } + + private static FishTable CachedManagedObjectSizes => _cachedManagedObjectSizes ??= new(); + + [ThreadStatic] + private static FishTable? _cachedManagedObjectSizes; + + private struct ManagedObjectSize + { + public uint Size = uint.MaxValue; + + [UsedImplicitly] + public ManagedObjectSize() + { + } + } public static GenericBase GetListExtensionMethods(Type type) => ListExtensionCache.TryGetValue(type) - ?? CreateListExtensionMethods(type); + ?? CreateListExtensionMethods(type); private static GenericBase CreateListExtensionMethods(Type type) { @@ -322,7 +732,8 @@ public static class CollectionExtensions public class Generic : GenericBase { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void AddRange(IList list, ICollection collection) => ((List)list).AddRange((IEnumerable)collection); + public override void AddRange(IList list, ICollection collection) + => ((List)list).AddRange((IEnumerable)collection); [MethodImpl(MethodImplOptions.AggressiveInlining)] public override Array ToArray(IList list) => ((List)list).ToArray(); diff --git a/Source/FisheryLib/Collections/FishSet.cs b/Source/FisheryLib/Collections/FishSet.cs new file mode 100644 index 0000000..ce432b3 --- /dev/null +++ b/Source/FisheryLib/Collections/FishSet.cs @@ -0,0 +1,862 @@ +// Copyright (c) 2023 bradson +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Loosely based on https://github.com/matthewcrews/FastDictionaryTest/tree/main +// which itself is based on +// https://probablydance.com/2018/05/28/a-new-fast-hash-table-in-response-to-googles-new-fast-hash-table/ + +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using FisheryLib.FunctionPointers; +using FisheryLib.Utility; +using JetBrains.Annotations; + +namespace FisheryLib.Collections; + +#pragma warning disable CS8766, CS8767 +[PublicAPI] +[StructLayout(LayoutKind.Sequential)] +public class FishSet : ISet, IReadOnlyCollection, ICollection +{ + private const int FIBONACCI_HASH = -1640531527; // unchecked((int)(uint)Math.Round(uint.MaxValue / FishMath.PHI)); + + internal T?[] _buckets; + + private unsafe delegate* _hashCodeGetter = GetHashCode.Default; + private unsafe delegate* _equalityComparer = Equals.Default; + + private int + _bucketBitShift, + _count; + + internal Tails _tails; + + private int + _wrapAroundMask, + _version; + + private float _maxLoadFactor = 0.5f; + + public int Version + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _version; + set + { + Guard.IsGreaterThan(value, _version); + _version = value; + } + } + + public int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _count; + } + + public object SyncRoot => throw new NotImplementedException(); + + public bool IsSynchronized => throw new NotImplementedException(); + + public bool IsReadOnly => false; + + public event Action? + EntryAdded, + EntryRemoved; + + public float MaxLoadFactor + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _maxLoadFactor; + set + { + Guard.IsInRange(value, 0f, 1f); + _maxLoadFactor = value; + } + } + + private static T AssertKeyType(object key) + { + if (key is T tKey) + return tKey; + else if (key != null!) + ThrowHelper.ThrowWrongKeyTypeArgumentException(key); + + return default!; + } + + public bool this[T key] + { + [CollectionAccess(CollectionAccessType.Read)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Contains(key); + + [CollectionAccess(CollectionAccessType.UpdatedContent)] + set => InsertEntry(key, value); + } + + public FishSet() : this(0) + { + } + + public FishSet(int minimumCapacity) => Initialize(minimumCapacity); + + [MemberNotNull(nameof(_buckets))] + [MemberNotNull(nameof(_tails))] + private void Initialize(int minimumCapacity = 0) + { + minimumCapacity = minimumCapacity <= 4 ? 4 : FishMath.NextPowerOfTwo(minimumCapacity); + // Mathf.NextPowerOfTwo(minimumCapacity); + + _buckets = new T?[minimumCapacity]; + _tails = new((uint)minimumCapacity); + _bucketBitShift = 32 - FishMath.TrailingZeroCount((uint)_buckets.Length); + _wrapAroundMask = _buckets.Length - 1; + } + + public FishSet(IEnumerable entries) : this(0) + { + foreach (var entry in entries) + InsertEntry(entry, false); + } + + private int GetTailIndex(int entryIndex) => GetIndexForTail(entryIndex, _tails[(uint)entryIndex]); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetIndexForTail(int entryIndex, uint tail) => (entryIndex + GetJumpDistance(tail)) & _wrapAroundMask; + + #region Junk + + // private static long GetJumpDistanceLong(byte index) + // => index switch + // { + // 0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7, + // 8 => 8, 9 => 9, 10 => 10, 11 => 11, 12 => 12, 13 => 13, 14 => 14, + // 15 => 15, 16 => 21, 17 => 28, 18 => 36, 19 => 45, 20 => 55, 21 => 66, + // 22 => 78, 23 => 91, 24 => 105, 25 => 120, 26 => 136, 27 => 153, + // 28 => 171, 29 => 190, 30 => 210, 31 => 231, 32 => 253, 33 => 276, + // 34 => 300, 35 => 325, 36 => 351, 37 => 378, 38 => 406, 39 => 435, + // 40 => 465, 41 => 496, 42 => 528, 43 => 561, 44 => 595, 45 => 630, + // 46 => 666, 47 => 703, 48 => 741, 49 => 780, 50 => 820, 51 => 861, + // 52 => 903, 53 => 946, 54 => 990, 55 => 1035, 56 => 1081, 57 => 1128, + // 58 => 1176, 59 => 1225, 60 => 1275, 61 => 1326, 62 => 1378, 63 => 1431, + // 64 => 1485, 65 => 1540, 66 => 1596, 67 => 1653, 68 => 1711, 69 => 1770, + // 70 => 1830, 71 => 1891, 72 => 1953, 73 => 2016, 74 => 2080, 75 => 2145, + // 76 => 2211, 77 => 2278, 78 => 2346, 79 => 2415, 80 => 2485, 81 => 2556, + // 82 => 3741, 83 => 8385, 84 => 18915, 85 => 42486, 86 => 95703, 87 => 215496, + // 88 => 485605, 89 => 1091503, 90 => 2456436, 91 => 5529475, 92 => 12437578, + // 93 => 27986421, 94 => 62972253, 95 => 141700195, 96 => 318819126, 97 => 717314626, + // 98 => 1614000520, 99 => 3631437253, 100 => 8170829695, 101 => 18384318876, 102 => 41364501751, + // 103 => 93070021080, 104 => 209407709220, 105 => 471167588430, 106 => 1060127437995, 107 => 2385287281530, + // 108 => 5366895564381, 109 => 12075513791265, 110 => 27169907873235, 111 => 61132301007778, + // 112 => 137547673121001, 113 => 309482258302503, 114 => 696335090510256, 115 => 1566753939653640, + // 116 => 3525196427195653, 117 => 7931691866727775, 118 => 17846306747368716, + // 119 => 40154190394120111, 120 => 90346928493040500, 121 => 203280588949935750, + // 122 => 457381324898247375, 123 => 1029107980662394500, 124 => 2315492957028380766, + // 125 => 5209859150892887590, > 125 => throw new() + // }; + + // private static int GetJumpDistance(byte index) + // => index switch + // { + // 0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7, 8 => 8, 9 => 9, 10 => 10, 11 => 11, + // 12 => 12, 13 => 13, 14 => 14, 15 => 15, 16 => 21, 17 => 28, 18 => 36, 19 => 45, 20 => 55, 21 => 66, + // 22 => 78, 23 => 91, 24 => 105, 25 => 120, 26 => 136, 27 => 153, 28 => 171, 29 => 190, 30 => 210, 31 => 231, + // 32 => 253, 33 => 276, 34 => 300, 35 => 325, 36 => 351, 37 => 378, 38 => 406, 39 => 435, 40 => 465, + // 41 => 496, 42 => 528, 43 => 561, 44 => 595, 45 => 630, 46 => 666, 47 => 703, 48 => 741, 49 => 780, + // 50 => 820, 51 => 861, 52 => 903, 53 => 946, 54 => 990, 55 => 1035, 56 => 1081, 57 => 1128, 58 => 1176, + // 59 => 1225, 60 => 1275, 61 => 1326, 62 => 1378, 63 => 1431, 64 => 1485, 65 => 1540, 66 => 1596, 67 => 1653, + // 68 => 1711, 69 => 1770, 70 => 1830, 71 => 1891, 72 => 1953, 73 => 2016, 74 => 2080, 75 => 2145, 76 => 2211, + // 77 => 2278, 78 => 2346, 79 => 2415, 80 => 2485, 81 => 2556, 82 => 3741, 83 => 8385, 84 => 18915, + // 85 => 42486, 86 => 95703, 87 => 215496, 88 => 485605, 89 => 1091503, 90 => 2456436, 91 => 5529475, + // 92 => 12437578, 93 => 27986421, 94 => 62972253, 95 => 141700195, 96 => 318819126, 97 => 717314626, + // 98 => 1614000520, 255 => int.MaxValue, > 98 => ThrowForInvalidIndex(index) + // }; + + #endregion + + private static int GetJumpDistance(uint index) + => index switch + { + 2 => 1, 3 => 2, 4 => 3, 5 => 4, 6 => 5, 7 => 8, 8 => 13, 9 => 21, 10 => 34, 11 => 55, 12 => 89, 13 => 144, + 14 => 233, 15 => 377, _ => MultiplyWithPhi(index) + }; + + [MethodImpl(MethodImplOptions.NoInlining)] + [DoesNotReturn] + private static int MultiplyWithPhi(uint index) + => throw new NotImplementedException(); // (int)(uint)Math.Round(index * FishMath.PHI); + + // private static int ThrowForInvalidIndex(byte index) + // => throw new($"Jump distance index for FishTable too large. Should be < 99, got {index} instead."); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsTail(int bucketIndex) + => bucketIndex != GetBucketIndexForKeyAt(bucketIndex); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsTail(T entry, int bucketIndex) + => bucketIndex != GetBucketIndex(entry); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool HasTail(int entryIndex) => !_tails.IsSoloOrEmpty(entryIndex); + + private void SetEntryWithoutTail(int bucketIndex, T entry) + { + _buckets[bucketIndex] = entry; + _tails.SetSolo(bucketIndex); + } + + private void SetEntryAsTail(int bucketIndex, T entry, int parentIndex, uint offset) + { + SetEntryWithoutTail(bucketIndex, entry); + _tails[(uint)parentIndex] = offset; + } + + private void SetParentTail(int entryIndex, uint newTail) + => _tails[(uint)GetParentBucketIndex(entryIndex)] = newTail; + + private bool InsertEntry(T entry, bool allowReplace = true, bool shifting = false) + { + var addedNewEntry = InsertEntryInternal(entry, allowReplace); + + if (shifting) + return addedNewEntry; + + _version++; + + if (!addedNewEntry) + return false; + + OnEntryAdded(entry); + return true; + } + + private void OnEntryAdded(T entry) + { + // _version++; handled separately + _count++; + EntryAdded?.Invoke(entry); + } + + /// + /// Returns true when adding a new entry, false for replacing an existing entry. AllowReplace: false causes throwing + /// instead. Does not adjust Count, Version or invoke EntryAdded. + /// + private bool InsertEntryInternal(T entry, bool allowReplace = true) + { + StartOfMethod: + var bucketIndex = GetBucketIndex(entry); + + ref var bucket = ref _buckets[bucketIndex]; + if (IsBucketEmpty(bucketIndex)) + { + SetEntryWithoutTail(bucketIndex, entry); + return true; + } + + if (TryReplaceValueOfMatchingKey(entry, allowReplace, ref bucket!)) + return false; + + if (CheckResize()) + goto StartOfMethod; + + if (IsTail(bucket, bucketIndex)) + { + var previousEntry = bucket; + var hasTail = !_tails.IsSolo(bucketIndex); + var tailingEntries = hasTail ? new T[GetTailCount(bucketIndex)] : null; + + if (hasTail) + { + var i = 0; + var tailIndex = GetTailIndex(bucketIndex); + + while (true) + { + ref var tailingBucket = ref _buckets[tailIndex]; + tailingEntries![i++] = tailingBucket!; + tailingBucket = default; + + if (!_tails.IsSoloOrEmpty(tailIndex)) + { + var nextTailIndex = GetTailIndex(tailIndex); + _tails.SetEmpty(tailIndex); + tailIndex = nextTailIndex; + } + else + { + _tails.SetEmpty(tailIndex); + break; + } + } + } + + SetParentTail(bucketIndex, Tails.SOLO); + SetEntryWithoutTail(bucketIndex, entry); + + InsertEntry(previousEntry, shifting: true); + if (hasTail) + { + for (var i = 0; i < tailingEntries!.Length; i++) + InsertEntry(tailingEntries[i], shifting: true); + } + + return true; + } + else + { + return !TryFindTailIndexAndReplace(entry, ref bucketIndex, allowReplace) + && InsertAsTail(entry, bucketIndex); + } + } + + private int GetTailCount(int index) + { + var tailCount = 0; + + while (HasTail(index)) + { + tailCount++; + index = GetTailIndex(index); + } + + return tailCount; + } + + private bool IsBucketEmpty(int bucketIndex) => IsBucketEmpty(bucketIndex, _tails, _buckets); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsBucketEmpty(int bucketIndex, Tails tails, T?[] buckets) => tails.IsEmpty(bucketIndex); + + private bool TryFindTailIndexAndReplace(T entry, ref int bucketIndex, bool allowReplace) + { + while (true) + { + if (!TryContinueWithTail(ref bucketIndex)) + return false; + + if (TryReplaceValueOfMatchingKey(entry, allowReplace, ref _buckets[bucketIndex]!)) + return true; + } + } + + private unsafe bool TryReplaceValueOfMatchingKey(T entry, bool allowReplace, ref T bucket) + { + if (!_equalityComparer(bucket, entry)) + return false; + + if (!allowReplace) + ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(entry); + + bucket = entry; + return true; + } + + private bool InsertAsTail(T entry, int bucketIndex) + { + var parentIndex = bucketIndex; + var offset = 2u; + + while (true) + { + bucketIndex = GetIndexForTail(parentIndex, offset); + + if (IsBucketEmpty(bucketIndex)) + { + SetEntryAsTail(bucketIndex, entry, parentIndex, offset); + return true; + } + + if (IsTail(bucketIndex) && offset < _tails[(uint)GetParentBucketIndex(bucketIndex)]) + break; + + if (++offset <= 15u) + continue; + + if (_buckets.Length > Count * 5) + WarnForPossiblyExcessiveResizing(entry); + + Resize(); + InsertEntry(entry, false, true); + return true; + } + + var previousEntry = _buckets[bucketIndex]; + var hasTail = !_tails.IsSolo(bucketIndex); + + var tailingEntries = hasTail ? new T?[GetTailCount(bucketIndex)] : null; + + if (hasTail) + { + var k = 0; + var tailIndex = GetTailIndex(bucketIndex); + + while (true) + { + ref var tailingBucket = ref _buckets[tailIndex]; + tailingEntries![k] = tailingBucket; + + k++; + tailingBucket = default; + + if (!_tails.IsSoloOrEmpty(tailIndex)) + { + var nextTailIndex = GetTailIndex(tailIndex); + _tails.SetEmpty(tailIndex); + tailIndex = nextTailIndex; + } + else + { + _tails.SetEmpty(tailIndex); + break; + } + } + } + + SetParentTail(bucketIndex, Tails.SOLO); + SetEntryAsTail(bucketIndex, entry, parentIndex, offset); + + InsertEntry(previousEntry!, shifting: true); + + if (hasTail) + { + for (var k = 0; k < tailingEntries!.Length; k++) + InsertEntry(tailingEntries[k]!, shifting: true); + } + + return true; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void WarnForPossiblyExcessiveResizing(T entry) + => Log.Warning($"FishTable is resizing from a large number of clashing hashCodes. Last inserted key: '{ + entry}', count: '{_count}', bucket array length: '{ + _buckets.Length}', tailing entries: '{GetTailingEntriesCount()}'"); + + private int GetTailingEntriesCount() + { + var count = 0; + for (var i = _buckets.Length; i-- > 0;) + { + if (!IsBucketEmpty(i) && IsTail(i)) + count++; + } + + return count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool CheckResize() + { + if (_count <= (int)(_buckets.Length * _maxLoadFactor)) + return false; + + Resize(); + return true; + } + + private void Resize() + { + var oldBuckets = _buckets; + var oldTails = _tails; + + _tails = new((uint)_buckets.Length << 1); + _buckets = new T?[_buckets.Length << 1]; + + _bucketBitShift = 32 - FishMath.TrailingZeroCount((uint)_buckets.Length); + _wrapAroundMask = _buckets.Length - 1; + + for (var i = 0; i < oldBuckets.Length; i++) + { + if (!IsBucketEmpty(i, oldTails, oldBuckets)) + InsertEntry(oldBuckets[i]!, false, true); + } + } + + [CollectionAccess(CollectionAccessType.Read)] + public unsafe bool Contains(T key) + { + var bucketIndex = GetBucketIndex(key); + + while (true) + { + if (_equalityComparer(key, _buckets[bucketIndex])) + return true; + + if (!TryContinueWithTail(ref bucketIndex)) + return false; + } + } + + #region GetBucketIndexMethods + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe int GetBucketIndex(T key) => GetBucketIndex(_hashCodeGetter(key)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetBucketIndex(int hashCode) => (hashCode * FIBONACCI_HASH) >>> _bucketBitShift; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetBucketIndexForKeyAt(int index) => GetBucketIndex(_buckets[index]!); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetParentBucketIndex(int childBucketIndex) + { + var ancestorIndex = GetBucketIndexForKeyAt(childBucketIndex); + + var i = 0; + + while (true) + { + if (i++ > 32) + ThrowHelper.ThrowFailedToFindParentInvalidOperationException(this, childBucketIndex); + + var nextIndex = GetTailIndex(ancestorIndex); + if (nextIndex == childBucketIndex) + break; + + ancestorIndex = nextIndex; + } + + return ancestorIndex; + } + + #endregion + + /// + /// Returns false for solo or empty, otherwise sets the index to the tail index and returns true + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private bool TryContinueWithTail(ref int entryIndex) + { + if (!HasTail(entryIndex)) + return false; + + entryIndex = GetTailIndex(entryIndex); + return true; + } + + [CollectionAccess(CollectionAccessType.UpdatedContent)] + public bool Add(T key) + { + Guard.IsNotNull(key); + return InsertEntry(key, false); + } + + public void UnionWith(IEnumerable other) => throw new NotImplementedException(); + + public void IntersectWith(IEnumerable other) => throw new NotImplementedException(); + + public void ExceptWith(IEnumerable other) => throw new NotImplementedException(); + + public void SymmetricExceptWith(IEnumerable other) => throw new NotImplementedException(); + + public bool IsSubsetOf(IEnumerable other) => throw new NotImplementedException(); + + public bool IsSupersetOf(IEnumerable other) => throw new NotImplementedException(); + + public bool IsProperSupersetOf(IEnumerable other) => throw new NotImplementedException(); + + public bool IsProperSubsetOf(IEnumerable other) => throw new NotImplementedException(); + + public bool Overlaps(IEnumerable other) => throw new NotImplementedException(); + + public bool SetEquals(IEnumerable other) => throw new NotImplementedException(); + + [CollectionAccess(CollectionAccessType.ModifyExistingContent)] + public bool Remove(T key) + { + if (!RemoveInternal(key)) + return false; + + OnEntryRemoved(key); + return true; + } + + private void EmplaceWithTails(int entryIndex) + { + ref var bucket = ref _buckets[entryIndex]; + var entry = bucket; + + Span tailIndices = stackalloc int[GetTailCount(entryIndex)]; + + if (HasTail(entryIndex)) + { + tailIndices[0] = GetTailIndex(entryIndex); + for (var i = 1; i < tailIndices.Length; i++) + tailIndices[i] = GetTailIndex(tailIndices[i - 1]); + } + + if (IsTail(entry!, entryIndex)) + SetParentTail(entryIndex, Tails.SOLO); + + bucket = default; + _tails.SetEmpty(entryIndex); + InsertEntry(entry!, shifting: true); + + for (var i = 1; i < tailIndices.Length; i++) + { + var tailIndex = tailIndices[i]; + ref var tailBucket = ref _buckets[tailIndex]; + var tailEntry = tailBucket; + + tailBucket = default; + _tails.SetEmpty(tailIndex); + InsertEntry(tailEntry!, shifting: true); + } + } + + private unsafe bool RemoveInternal(T key) + { + var bucketIndex = GetBucketIndex(key); + + while (true) + { + ref var entry = ref _buckets[bucketIndex]; + + if (_equalityComparer(key, entry)) + { + var tailIndex = -1; + if (IsTail(entry!, bucketIndex)) + { + if (HasTail(bucketIndex)) + tailIndex = GetTailIndex(bucketIndex); + else + SetParentTail(bucketIndex, Tails.SOLO); + } + + entry = default; + _tails.SetEmpty(bucketIndex); + + if (tailIndex != -1) + EmplaceWithTails(tailIndex); + + return true; + } + + if (TryContinueWithTail(ref bucketIndex)) + continue; + + return false; + } + } + + void ICollection.Add(T item) => Add(item); + + [CollectionAccess(CollectionAccessType.ModifyExistingContent)] + public void Clear() + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (_buckets is null) + Initialize(); + + Array.Clear(_buckets, 0, _buckets.Length); + _tails.Reset(); + + _count = 0; + _version++; + } + + [CollectionAccess(CollectionAccessType.Read)] + public void CopyTo(T[] array, int arrayIndex) + { + var length = _buckets.Length; + for (var i = 0; i < length; i++) + { + if (IsBucketEmpty(i)) + continue; + + array[arrayIndex] = _buckets[i]!; + arrayIndex++; + } + } + + private void OnEntryRemoved(T entry) + { + _version++; + _count--; + EntryRemoved?.Invoke(entry); + } + + #region InterfaceMethods + + [CollectionAccess(CollectionAccessType.Read)] + public Enumerator GetEnumerator() => new(this, Enumerator.KEY_VALUE_PAIR); + + IEnumerator IEnumerable.GetEnumerator() + => new Enumerator(this, Enumerator.KEY_VALUE_PAIR); + + IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this, Enumerator.KEY_VALUE_PAIR); + + void ICollection.CopyTo(Array array, int index) + { + if (array is T[] typedArray) + { + CopyTo(typedArray, index); + return; + } + + var length = _buckets.Length; + for (var i = 0; i < length; i++) + { + if (IsBucketEmpty(i)) + continue; + + ((IList)array)[index] = _buckets[i]; + index++; + } + } + + #endregion + + #region Structs + + internal struct Tails + { + public const uint + EMPTY = 0u, + SOLO = 1u; + + private NibbleArray _values; + + public Tails(uint length) => _values = new(length); + + public uint this[uint index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _values[index]; + + set => _values[index] = value; + } + + public void Reset() => _values.Clear(); //_values.Initialize(15u); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsEmpty(int index) => _values[(uint)index] == EMPTY; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsSolo(int index) => _values[(uint)index] == SOLO; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsSoloOrEmpty(int index) => _values[(uint)index] <= 1u; + + public void SetSolo(int index) => _values[(uint)index] = SOLO; + + public void SetEmpty(int index) => _values[(uint)index] = EMPTY; + } + + public record struct Enumerator : IEnumerator + { + private readonly FishSet _dictionary; + private readonly int _version; + private uint _index; + private T? _current; + + internal const int DICT_ENTRY = 1; + internal const int KEY_VALUE_PAIR = 2; + + internal Enumerator(FishSet dictionary, int getEnumeratorRetType) + { + _dictionary = dictionary; + _version = dictionary._version; + _index = 0u; + _current = default; + } + + public bool MoveNext() + { + if (_version != _dictionary._version) + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); + + while (_index < (uint)_dictionary._buckets.Length) + { + if (_dictionary.IsBucketEmpty((int)_index++)) + continue; + + _current = _dictionary._buckets[_index - 1]!; + return true; + } + + _index = (uint)_dictionary._buckets.Length + 1u; + _current = default; + return false; + } + + [CollectionAccess(CollectionAccessType.Read)] + public T Current => _current!; + + public void Dispose() + { + } + + object IEnumerator.Current + { + get + { + if (_index == 0u || _index == (uint)_dictionary._buckets.Length + 1u) + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); + + return _current!; + } + } + + void IEnumerator.Reset() + { + if (_version != _dictionary._version) + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); + + _index = 0u; + _current = default; + } + } + + #endregion + + private static class ThrowHelper + { + [DoesNotReturn] + internal static void ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion() + => throw new InvalidOperationException("Collection was modified; enumeration operation may not execute."); + + [DoesNotReturn] + internal static void ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen() + => throw new InvalidOperationException("Enumeration has either not started or has already finished."); + + [DoesNotReturn] + internal static void ThrowWrongKeyTypeArgumentException(object? key) + { + Guard.IsNotNull(key); + throw new ArgumentException($"The value \"{key}\" is not of type \"{ + typeof(T)}\" and cannot be used in this generic collection.", + nameof(key)); + } + + [DoesNotReturn] + internal static void ThrowAddingDuplicateWithKeyArgumentException(T key) + { + Guard.IsNotNull(key); + ThrowAddingDuplicateWithKeyArgumentException((object)key); + } + + [DoesNotReturn] + internal static unsafe void ThrowFailedToFindParentInvalidOperationException(FishSet fishTable, + int childBucketIndex) + { + var entry = fishTable._buckets[childBucketIndex]; + throw new InvalidOperationException($"Failed to find parent index in FishTable for key: '{ + entry}', hashCode: '{fishTable._hashCodeGetter(entry)}', count: '{ + fishTable._count}', bucket array length: '{fishTable._buckets.Length}', tailing entries: '{ + fishTable.GetTailingEntriesCount()}'"); + } + + [DoesNotReturn] + private static void ThrowAddingDuplicateWithKeyArgumentException(object key) + => throw new ArgumentException($"An item with the same key has already been added. Key: {key}"); + } +} +#pragma warning restore CS8766, CS8767 \ No newline at end of file diff --git a/Source/FisheryLib/Collections/FishTable.cs b/Source/FisheryLib/Collections/FishTable.cs new file mode 100644 index 0000000..718249d --- /dev/null +++ b/Source/FisheryLib/Collections/FishTable.cs @@ -0,0 +1,1475 @@ +// Copyright (c) 2023 bradson +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Loosely based on https://github.com/matthewcrews/FastDictionaryTest/tree/main +// which itself is based on +// https://probablydance.com/2018/05/28/a-new-fast-hash-table-in-response-to-googles-new-fast-hash-table/ + +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using FisheryLib.FunctionPointers; +using FisheryLib.Utility; +using JetBrains.Annotations; + +namespace FisheryLib.Collections; + +#pragma warning disable CS8766, CS8767 +[PublicAPI] +[StructLayout(LayoutKind.Sequential)] +public class FishTable : IDictionary, IDictionary, IReadOnlyDictionary +{ + private const int FIBONACCI_HASH = -1640531527; // unchecked((int)(uint)Math.Round(uint.MaxValue / FishMath.PHI)); + + internal Entry[] _buckets; + + private unsafe delegate* _keyHashCodeByRefGetter = GetHashCodeByRef.Default; + private unsafe delegate* _keyEqualityByRefComparer = EqualsByRef.Default; + + private int + _bucketBitShift, + _count; + + private unsafe delegate* _keyHashCodeGetter = GetHashCode.Default; + private unsafe delegate* _keyEqualityComparer = Equals.Default; + + internal Tails _tails; + + private int + _wrapAroundMask, + _version; + + private float _maxLoadFactor = 0.5f; + + public ICollection Keys => throw new NotImplementedException(); + + public ICollection Values => throw new NotImplementedException(); + + public int Version + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _version; + set + { + Guard.IsGreaterThan(value, _version); + _version = value; + } + } + + public int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _count; + } + + public bool IsReadOnly => false; + + public unsafe uint SizeEstimate + => GetType().ComputeManagedObjectSizeEstimate() + + ((uint)sizeof(Entry) * (uint)_buckets.Length) + + ((_tails.Length + 1u) >> 1); + + public event Action>? + EntryAdded, + EntryRemoved; + + public delegate void EntryEventHandler(TKey key, ref TValue value); + + public event Action? KeyAdded + { + add => AddEvent(ref EntryAdded, value); + remove => RemoveEvent(ref EntryAdded, value); + } + + public event Action? KeyRemoved + { + add => AddEvent(ref EntryRemoved, value); + remove => RemoveEvent(ref EntryRemoved, value); + } + + public event Action? ValueAdded + { + add => AddEvent(ref EntryAdded, value); + remove => RemoveEvent(ref EntryAdded, value); + } + + public event Action? ValueRemoved + { + add => AddEvent(ref EntryRemoved, value); + remove => RemoveEvent(ref EntryRemoved, value); + } + + private static void AddEvent(ref Action>? @event, Action? value) + where THandler : IEventHandler + { + if (value != null) + @event += Reflection.New>(value).Invoke; + } + + private static void RemoveEvent(ref Action>? @event, Action? value) + { + if (@event is null) + return; + + var delegates = @event.GetInvocationList(); + for (var i = delegates.Length; i-- > 0;) + { + if (delegates[i].Target is not IEventHandler handler || handler.Action != value) + continue; + + @event -= (Action>)delegates[i]; + return; + } + } + + private interface IEventHandler + { + public Action Action { get; } + public void Invoke(KeyValuePair pair); + } + + private record KeyEventHandler(Action Action) : IEventHandler + { + public void Invoke(KeyValuePair pair) => Action(pair.Key); + } + + private record ValueEventHandler(Action Action) : IEventHandler + { + public void Invoke(KeyValuePair pair) => Action(pair.Value); + } + + public float MaxLoadFactor + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _maxLoadFactor; + set + { + Guard.IsInRange(value, 0f, 1f); + _maxLoadFactor = value; + } + } + + ICollection IDictionary.Keys => throw new NotImplementedException(); + + ICollection IDictionary.Values => throw new NotImplementedException(); + + bool IDictionary.IsFixedSize => false; + + object ICollection.SyncRoot => this; // matching System.Collections.Generic.Dictionary + + bool ICollection.IsSynchronized => false; + + IEnumerable IReadOnlyDictionary.Keys => throw new NotImplementedException(); + + IEnumerable IReadOnlyDictionary.Values => throw new NotImplementedException(); + + object? IDictionary.this[object key] + { + get => this[AssertKeyType(key)]; + set => this[AssertKeyType(key)] = AssertValueType(value); + } + + private static TKey AssertKeyType(object key) + { + if (key is TKey tKey) + return tKey; + else if (key != null!) + ThrowHelper.ThrowWrongKeyTypeArgumentException(key); + + return default!; + } + + private static TValue AssertValueType(object? value) + { + if (value is TValue tValue) + return tValue; + else if (value != null!) + ThrowHelper.ThrowWrongValueTypeArgumentException(value); + + return default!; + } + + public unsafe TValue? this[TKey key] + { + [CollectionAccess(CollectionAccessType.Read)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + var bucketIndex = GetBucketIndex(key); + + while (true) + { + ref var bucket = ref _buckets[bucketIndex]; + + if (_keyEqualityComparer(key, bucket.Key)) + return bucket.Value; + + ContinueWithTailOrThrow(ref bucketIndex, key); + } + } + + [CollectionAccess(CollectionAccessType.UpdatedContent)] + set => InsertEntry(key, value); + } + + public FishTable() : this(0) + { + } + + public FishTable(int minimumCapacity) => Initialize(minimumCapacity); + + [MemberNotNull(nameof(_buckets))] + [MemberNotNull(nameof(_tails))] + private void Initialize(int minimumCapacity = 0) + { + minimumCapacity = minimumCapacity <= 4 ? 4 : FishMath.NextPowerOfTwo(minimumCapacity); + // Mathf.NextPowerOfTwo(minimumCapacity); + + _buckets = new Entry[minimumCapacity]; + _tails = new((uint)minimumCapacity); + _bucketBitShift = 32 - FishMath.TrailingZeroCount((uint)_buckets.Length); + _wrapAroundMask = _buckets.Length - 1; + } + + public FishTable(IEnumerable> entries) : this(0) + => AddRange(entries); + + private int GetTailIndex(int entryIndex) => GetIndexForTail(entryIndex, _tails[(uint)entryIndex]); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetIndexForTail(int entryIndex, uint tail) => (entryIndex + GetJumpDistance(tail)) & _wrapAroundMask; + + #region Junk + + // private static long GetJumpDistanceLong(byte index) + // => index switch + // { + // 0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7, + // 8 => 8, 9 => 9, 10 => 10, 11 => 11, 12 => 12, 13 => 13, 14 => 14, + // 15 => 15, 16 => 21, 17 => 28, 18 => 36, 19 => 45, 20 => 55, 21 => 66, + // 22 => 78, 23 => 91, 24 => 105, 25 => 120, 26 => 136, 27 => 153, + // 28 => 171, 29 => 190, 30 => 210, 31 => 231, 32 => 253, 33 => 276, + // 34 => 300, 35 => 325, 36 => 351, 37 => 378, 38 => 406, 39 => 435, + // 40 => 465, 41 => 496, 42 => 528, 43 => 561, 44 => 595, 45 => 630, + // 46 => 666, 47 => 703, 48 => 741, 49 => 780, 50 => 820, 51 => 861, + // 52 => 903, 53 => 946, 54 => 990, 55 => 1035, 56 => 1081, 57 => 1128, + // 58 => 1176, 59 => 1225, 60 => 1275, 61 => 1326, 62 => 1378, 63 => 1431, + // 64 => 1485, 65 => 1540, 66 => 1596, 67 => 1653, 68 => 1711, 69 => 1770, + // 70 => 1830, 71 => 1891, 72 => 1953, 73 => 2016, 74 => 2080, 75 => 2145, + // 76 => 2211, 77 => 2278, 78 => 2346, 79 => 2415, 80 => 2485, 81 => 2556, + // 82 => 3741, 83 => 8385, 84 => 18915, 85 => 42486, 86 => 95703, 87 => 215496, + // 88 => 485605, 89 => 1091503, 90 => 2456436, 91 => 5529475, 92 => 12437578, + // 93 => 27986421, 94 => 62972253, 95 => 141700195, 96 => 318819126, 97 => 717314626, + // 98 => 1614000520, 99 => 3631437253, 100 => 8170829695, 101 => 18384318876, 102 => 41364501751, + // 103 => 93070021080, 104 => 209407709220, 105 => 471167588430, 106 => 1060127437995, 107 => 2385287281530, + // 108 => 5366895564381, 109 => 12075513791265, 110 => 27169907873235, 111 => 61132301007778, + // 112 => 137547673121001, 113 => 309482258302503, 114 => 696335090510256, 115 => 1566753939653640, + // 116 => 3525196427195653, 117 => 7931691866727775, 118 => 17846306747368716, + // 119 => 40154190394120111, 120 => 90346928493040500, 121 => 203280588949935750, + // 122 => 457381324898247375, 123 => 1029107980662394500, 124 => 2315492957028380766, + // 125 => 5209859150892887590, > 125 => throw new() + // }; + + // private static int GetJumpDistance(byte index) + // => index switch + // { + // 0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7, 8 => 8, 9 => 9, 10 => 10, 11 => 11, + // 12 => 12, 13 => 13, 14 => 14, 15 => 15, 16 => 21, 17 => 28, 18 => 36, 19 => 45, 20 => 55, 21 => 66, + // 22 => 78, 23 => 91, 24 => 105, 25 => 120, 26 => 136, 27 => 153, 28 => 171, 29 => 190, 30 => 210, 31 => 231, + // 32 => 253, 33 => 276, 34 => 300, 35 => 325, 36 => 351, 37 => 378, 38 => 406, 39 => 435, 40 => 465, + // 41 => 496, 42 => 528, 43 => 561, 44 => 595, 45 => 630, 46 => 666, 47 => 703, 48 => 741, 49 => 780, + // 50 => 820, 51 => 861, 52 => 903, 53 => 946, 54 => 990, 55 => 1035, 56 => 1081, 57 => 1128, 58 => 1176, + // 59 => 1225, 60 => 1275, 61 => 1326, 62 => 1378, 63 => 1431, 64 => 1485, 65 => 1540, 66 => 1596, 67 => 1653, + // 68 => 1711, 69 => 1770, 70 => 1830, 71 => 1891, 72 => 1953, 73 => 2016, 74 => 2080, 75 => 2145, 76 => 2211, + // 77 => 2278, 78 => 2346, 79 => 2415, 80 => 2485, 81 => 2556, 82 => 3741, 83 => 8385, 84 => 18915, + // 85 => 42486, 86 => 95703, 87 => 215496, 88 => 485605, 89 => 1091503, 90 => 2456436, 91 => 5529475, + // 92 => 12437578, 93 => 27986421, 94 => 62972253, 95 => 141700195, 96 => 318819126, 97 => 717314626, + // 98 => 1614000520, 255 => int.MaxValue, > 98 => ThrowForInvalidIndex(index) + // }; + + #endregion + + private static int GetJumpDistance(uint index) + => index switch + { + 2 => 1, 3 => 2, 4 => 3, 5 => 4, 6 => 5, 7 => 8, 8 => 13, 9 => 21, 10 => 34, 11 => 55, 12 => 89, 13 => 144, + 14 => 233, 15 => 377, _ => MultiplyWithPhi(index) + }; + + [MethodImpl(MethodImplOptions.NoInlining)] + [DoesNotReturn] + private static int MultiplyWithPhi(uint index) + => throw new NotImplementedException(); // (int)(uint)Math.Round(index * FishMath.PHI); + + // private static int ThrowForInvalidIndex(byte index) + // => throw new($"Jump distance index for FishTable too large. Should be < 99, got {index} instead."); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsTail(int bucketIndex) + => bucketIndex != GetBucketIndexForKeyAt(bucketIndex); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsTail(ref Entry entry, int bucketIndex) + => bucketIndex != GetBucketIndex(entry.Key); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool HasTail(int entryIndex) => !_tails.IsSoloOrEmpty(entryIndex); + + private void SetEntryWithoutTail(int bucketIndex, in Entry entry) + { + _buckets[bucketIndex] = entry; + _tails.SetSolo(bucketIndex); + } + + private void SetEntryAsTail(int bucketIndex, in Entry entry, int parentIndex, uint offset) + { + SetEntryWithoutTail(bucketIndex, entry); + _tails[(uint)parentIndex] = offset; + } + + private void SetParentTail(int entryIndex, uint newTail) + => _tails[(uint)GetParentBucketIndex(entryIndex)] = newTail; + + private void InsertEntry(TKey key, TValue? value, bool allowReplace = true, bool shifting = false) + => InsertEntry(new(key, value), allowReplace, shifting); + + private void InsertEntry(in Entry entry, bool allowReplace = true, bool shifting = false) + { + var addedNewEntry = InsertEntryInternal(entry, allowReplace); + + if (shifting) + return; + + _version++; + + if (!addedNewEntry) + return; + + OnEntryAdded(entry.AsKeyValuePair()); + } + + private void OnEntryAdded(in KeyValuePair entry) + { + // _version++; handled separately + _count++; + EntryAdded?.Invoke(entry); + } + + /// + /// Returns true when adding a new entry, false for replacing an existing entry. AllowReplace: false causes throwing + /// instead. Does not adjust Count, Version or invoke EntryAdded. + /// + private bool InsertEntryInternal(in Entry entry, bool allowReplace = true) + { + StartOfMethod: + var bucketIndex = GetBucketIndex(entry.Key); + + ref var bucket = ref _buckets[bucketIndex]; + if (IsBucketEmpty(bucketIndex)) + { + SetEntryWithoutTail(bucketIndex, entry); + return true; + } + + if (TryReplaceValueOfMatchingKey(entry, allowReplace, ref bucket)) + return false; + + if (CheckResize()) + goto StartOfMethod; + + if (IsTail(ref bucket, bucketIndex)) + { + var previousEntry = bucket; + var hasTail = !_tails.IsSolo(bucketIndex); + var tailingEntries = hasTail ? new Entry[GetTailCount(bucketIndex)] : null; + + if (hasTail) + { + var i = 0; + var tailIndex = GetTailIndex(bucketIndex); + + while (true) + { + ref var tailingBucket = ref _buckets[tailIndex]; + tailingEntries![i++] = tailingBucket; + tailingBucket = default; + + if (!_tails.IsSoloOrEmpty(tailIndex)) + { + var nextTailIndex = GetTailIndex(tailIndex); + _tails.SetEmpty(tailIndex); + tailIndex = nextTailIndex; + } + else + { + _tails.SetEmpty(tailIndex); + break; + } + } + } + + SetParentTail(bucketIndex, Tails.SOLO); + SetEntryWithoutTail(bucketIndex, entry); + + InsertEntry(previousEntry, shifting: true); + if (hasTail) + { + for (var i = 0; i < tailingEntries!.Length; i++) + InsertEntry(tailingEntries[i], shifting: true); + } + + return true; + } + else + { + return !TryFindTailIndexAndReplace(entry, ref bucketIndex, allowReplace) + && InsertAsTail(entry, bucketIndex); + } + } + + private int GetTailCount(int index) + { + var tailCount = 0; + + while (HasTail(index)) + { + tailCount++; + index = GetTailIndex(index); + } + + return tailCount; + } + + private bool IsBucketEmpty(int bucketIndex) => IsBucketEmpty(bucketIndex, _tails, _buckets); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsBucketEmpty(int bucketIndex, Tails tails, Entry[] buckets) + => tails.IsEmpty(bucketIndex) && VerifyAgainstNullBucket(bucketIndex, tails, buckets); + + private unsafe bool VerifyAgainstNullBucket(int bucketIndex, Tails tails, Entry[] buckets) + { + ref var bucket = ref buckets[bucketIndex]; + + if (!_keyEqualityComparer(bucket.Key, default)) + ThrowHelper.ThrowEmptyBucketKeyInvalidOperationException(bucketIndex, bucket); + + if (bucket.Value.Equals(default)) + return true; + + // GetReference methods can get here when using a default key and accessing the returned ref + tails.SetSolo(bucketIndex); + OnEntryAdded(bucket.AsKeyValuePair()); // technically late, but it's an edge case and there's + return false; // no good workaround without performance hit + } + + private bool TryFindTailIndexAndReplace(in Entry entry, ref int bucketIndex, bool allowReplace) + { + while (true) + { + if (!TryContinueWithTail(ref bucketIndex)) + return false; + + if (TryReplaceValueOfMatchingKey(entry, allowReplace, ref _buckets[bucketIndex])) + return true; + } + } + + private unsafe bool TryReplaceValueOfMatchingKey(in Entry entry, bool allowReplace, ref Entry bucket) + { + if (!_keyEqualityComparer(bucket.Key, entry.Key)) + return false; + + if (!allowReplace) + ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(entry.Key); + + bucket.Value = entry.Value; + return true; + } + + private bool InsertAsTail(in Entry entry, int bucketIndex) + { + var parentIndex = bucketIndex; + var offset = 2u; + + while (true) + { + bucketIndex = GetIndexForTail(parentIndex, offset); + + if (IsBucketEmpty(bucketIndex)) + { + SetEntryAsTail(bucketIndex, entry, parentIndex, offset); + return true; + } + + if (IsTail(bucketIndex) && offset < _tails[(uint)GetParentBucketIndex(bucketIndex)]) + break; + + if (++offset <= 15u) + continue; + + if (_buckets.Length > Count * 5) + WarnForPossiblyExcessiveResizing(entry); + + Resize(); + InsertEntry(entry, false, true); + return true; + } + + var previousEntry = _buckets[bucketIndex]; + var hasTail = !_tails.IsSolo(bucketIndex); + + var tailingEntries = hasTail ? new Entry[GetTailCount(bucketIndex)] : null; + + if (hasTail) + { + var k = 0; + var tailIndex = GetTailIndex(bucketIndex); + + while (true) + { + ref var tailingBucket = ref _buckets[tailIndex]; + tailingEntries![k] = tailingBucket; + + k++; + tailingBucket = default; + + if (!_tails.IsSoloOrEmpty(tailIndex)) + { + var nextTailIndex = GetTailIndex(tailIndex); + _tails.SetEmpty(tailIndex); + tailIndex = nextTailIndex; + } + else + { + _tails.SetEmpty(tailIndex); + break; + } + } + } + + SetParentTail(bucketIndex, Tails.SOLO); + SetEntryAsTail(bucketIndex, entry, parentIndex, offset); + + InsertEntry(previousEntry, shifting: true); + + if (hasTail) + { + for (var k = 0; k < tailingEntries!.Length; k++) + InsertEntry(tailingEntries[k], shifting: true); + } + + return true; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void WarnForPossiblyExcessiveResizing(Entry entry) + => Log.Warning($"FishTable is resizing from a large number of clashing hashCodes. Last inserted key: '{ + entry.Key}', value: '{entry.Value}', count: '{_count}', bucket array length: '{ + _buckets.Length}', tailing entries: '{GetTailingEntriesCount()}'"); + + private int GetTailingEntriesCount() + { + var count = 0; + for (var i = _buckets.Length; i-- > 0;) + { + if (!IsBucketEmpty(i) && IsTail(i)) + count++; + } + + return count; + } + + public void EnsureCapacity(int minimumSize) + { + if (_buckets.Length >= minimumSize) + return; + + Resize(FishMath.NextPowerOfTwo(minimumSize)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool CheckResize() + { + if (_count <= (int)(_buckets.Length * _maxLoadFactor)) + return false; + + Resize(); + return true; + } + + private void Resize() => Resize(_buckets.Length << 1); + + private void Resize(int newSize) + { + var oldBuckets = _buckets; + var oldTails = _tails; + + _tails = new((uint)newSize); + _buckets = new Entry[newSize]; + + _bucketBitShift = 32 - FishMath.TrailingZeroCount((uint)newSize); + _wrapAroundMask = _buckets.Length - 1; + + for (var i = 0; i < oldBuckets.Length; i++) + { + if (!IsBucketEmpty(i, oldTails, oldBuckets)) + InsertEntry(oldBuckets[i], false, true); + } + } + + #region GetterMethods + + [CollectionAccess(CollectionAccessType.Read)] + public unsafe bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + { + var bucketIndex = GetBucketIndex(key); + + while (true) + { + ref var bucket = ref _buckets[bucketIndex]; + + if (_keyEqualityComparer(key, bucket.Key)) + { + value = bucket.Value!; + return true; + } + + if (TryContinueWithTail(ref bucketIndex)) + continue; + + value = default; + return false; + } + } + + [CollectionAccess(CollectionAccessType.Read)] + public unsafe TValue? TryGetValue(TKey key) + { + var bucketIndex = GetBucketIndex(key); + + while (true) + { + ref var bucket = ref _buckets[bucketIndex]; + + if (_keyEqualityComparer(key, bucket.Key)) + return bucket.Value; + + if (!TryContinueWithTail(ref bucketIndex)) + return default; + } + } + + [CollectionAccess(CollectionAccessType.Read | CollectionAccessType.UpdatedContent)] + public unsafe TValue GetOrAdd(TKey key) + { + StartOfMethod: + var bucketIndex = GetBucketIndex(key); + + while (true) + { + ref var bucket = ref _buckets[bucketIndex]; + + if (_keyEqualityComparer(key, bucket.Key)) + return bucket.Value!; + + if (!ContinueWithTailOrAddNew(ref bucketIndex, key)) + goto StartOfMethod; + } + } + + [CollectionAccess(CollectionAccessType.Read | CollectionAccessType.UpdatedContent)] + public unsafe TValue GetOrAdd(ref TKey key) + { + StartOfMethod: + var bucketIndex = GetBucketIndex(ref key); + + while (true) + { + ref var bucket = ref _buckets[bucketIndex]; + + if (_keyEqualityByRefComparer(ref key, ref bucket.Key)) + return bucket.Value!; + + if (!ContinueWithTailOrAddNew(ref bucketIndex, ref key)) + goto StartOfMethod; + } + } + + [CollectionAccess(CollectionAccessType.Read | CollectionAccessType.UpdatedContent)] + public unsafe ref TValue GetOrAddReference(TKey key) + { + StartOfMethod: + var bucketIndex = GetBucketIndex(key); + + while (true) + { + ref var bucket = ref _buckets[bucketIndex]; + + if (_keyEqualityComparer(key, bucket.Key)) + return ref bucket.Value!; + + if (!ContinueWithTailOrAddNew(ref bucketIndex, key)) + goto StartOfMethod; + } + } + + [CollectionAccess(CollectionAccessType.Read | CollectionAccessType.UpdatedContent)] + public unsafe ref TValue GetOrAddReference(ref TKey key) + { + StartOfMethod: + var bucketIndex = GetBucketIndex(ref key); + + while (true) + { + ref var bucket = ref _buckets[bucketIndex]; + + if (_keyEqualityByRefComparer(ref key, ref bucket.Key)) + return ref bucket.Value!; + + if (!ContinueWithTailOrAddNew(ref bucketIndex, ref key)) + goto StartOfMethod; + } + } + + [CollectionAccess(CollectionAccessType.Read | CollectionAccessType.ModifyExistingContent)] + public unsafe ref TValue GetReference(TKey key) + { + var bucketIndex = GetBucketIndex(key); + + while (true) + { + ref var bucket = ref _buckets[bucketIndex]; + + if (_keyEqualityComparer(key, bucket.Key)) + return ref bucket.Value!; + + ContinueWithTailOrThrow(ref bucketIndex, key); + } + } + + [CollectionAccess(CollectionAccessType.Read | CollectionAccessType.ModifyExistingContent)] + public unsafe ref TValue GetReference(ref TKey key) + { + var bucketIndex = GetBucketIndex(ref key); + + while (true) + { + ref var bucket = ref _buckets[bucketIndex]; + + if (_keyEqualityByRefComparer(ref key, ref bucket.Key)) + return ref bucket.Value!; + + ContinueWithTailOrThrow(ref bucketIndex, ref key); + } + } + + /// + /// Returns a reference to the value field of an entry if the key exists and an Unsafe.NullRef{TValue} if not. + /// Must be checked with Unsafe.IsNullRef before assigning to a regular var or field without ref. + /// + /// The key + /// ref TValue or Unsafe.NullRef{TValue} + [CollectionAccess(CollectionAccessType.Read | CollectionAccessType.ModifyExistingContent)] + public unsafe ref TValue? TryGetReferenceUnsafe(TKey key) + { + var bucketIndex = GetBucketIndex(key); + + while (true) + { + ref var bucket = ref _buckets[bucketIndex]; + + if (_keyEqualityComparer(key, bucket.Key)) + return ref bucket.Value; + + if (!TryContinueWithTail(ref bucketIndex)) + return ref Unsafe.NullRef(); + } + } + + [CollectionAccess(CollectionAccessType.Read | CollectionAccessType.ModifyExistingContent)] + public unsafe ref TValue? TryGetReferenceUnsafe(ref TKey key) + { + var bucketIndex = GetBucketIndex(ref key); + + while (true) + { + ref var bucket = ref _buckets[bucketIndex]; + + if (_keyEqualityByRefComparer(ref key, ref bucket.Key)) + return ref bucket.Value; + + if (!TryContinueWithTail(ref bucketIndex)) + return ref Unsafe.NullRef(); + } + } + + #endregion + + [CollectionAccess(CollectionAccessType.Read)] + public unsafe bool ContainsKey(TKey key) + { + var bucketIndex = GetBucketIndex(key); + + while (true) + { + if (_keyEqualityComparer(key, _buckets[bucketIndex].Key)) + return true; + + if (!TryContinueWithTail(ref bucketIndex)) + return false; + } + } + + #region GetBucketIndexMethods + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe int GetBucketIndex(ref TKey key) => GetBucketIndex(_keyHashCodeByRefGetter(ref key)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe int GetBucketIndex(TKey key) => GetBucketIndex(_keyHashCodeGetter(key)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetBucketIndex(int hashCode) => (hashCode * FIBONACCI_HASH) >>> _bucketBitShift; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetBucketIndexForKeyAt(int index) => GetBucketIndex(_buckets[index].Key); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetParentBucketIndex(int childBucketIndex) + { + var ancestorIndex = GetBucketIndexForKeyAt(childBucketIndex); + + var i = 0; + + while (true) + { + if (i++ > 32) + ThrowHelper.ThrowFailedToFindParentInvalidOperationException(this, childBucketIndex); + + var nextIndex = GetTailIndex(ancestorIndex); + if (nextIndex == childBucketIndex) + break; + + ancestorIndex = nextIndex; + } + + return ancestorIndex; + } + + #endregion + + #region ContinueWithTailMethods + + /// + /// Tries setting the index to the tail's index and returns true on success. If none exists, inserts a new entry + /// and returns false + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private bool ContinueWithTailOrAddNew(ref int entryIndex, TKey key) + { + if (TryContinueWithTail(ref entryIndex)) + return true; + + this[key] = Reflection.New(); + return false; + } + + /// + /// Tries setting the index to the tail's index and returns true on success. If none exists, inserts a new entry + /// and returns false + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private bool ContinueWithTailOrAddNew(ref int entryIndex, ref TKey key) + { + if (TryContinueWithTail(ref entryIndex)) + return true; + + this[key] = Reflection.New(); + return false; + } + + /// + /// Tries setting the index to the tail's index. If none exists, throws instead + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private void ContinueWithTailOrThrow(ref int entryIndex, TKey key) + { + if (!TryContinueWithTail(ref entryIndex)) + ThrowHelper.ThrowKeyNotFoundException(key); + } + + /// + /// Tries setting the index to the tail's index. If none exists, throws instead + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private void ContinueWithTailOrThrow(ref int entryIndex, ref TKey key) + { + if (!TryContinueWithTail(ref entryIndex)) + ThrowHelper.ThrowKeyNotFoundException(key); + } + + /// + /// Returns false for solo or empty, otherwise sets the index to the tail index and returns true + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private bool TryContinueWithTail(ref int entryIndex) + { + if (!HasTail(entryIndex)) + return false; + + entryIndex = GetTailIndex(entryIndex); + return true; + } + + #endregion + + [CollectionAccess(CollectionAccessType.UpdatedContent)] + public void Add(TKey key, TValue value) + { + Guard.IsNotNull(key); + InsertEntry(key, value, false); + } + + [CollectionAccess(CollectionAccessType.ModifyExistingContent)] + public bool Remove(TKey key) + { + if (!RemoveInternal(key, out var removedEntry)) + return false; + + OnEntryRemoved(removedEntry.AsKeyValuePair()); + return true; + } + + private void EmplaceWithTails(int entryIndex) + { + ref var bucket = ref _buckets[entryIndex]; + var entry = bucket; + + Span tailIndices = stackalloc int[GetTailCount(entryIndex)]; + + if (HasTail(entryIndex)) + { + tailIndices[0] = GetTailIndex(entryIndex); + for (var i = 1; i < tailIndices.Length; i++) + tailIndices[i] = GetTailIndex(tailIndices[i - 1]); + } + + if (IsTail(ref entry, entryIndex)) + SetParentTail(entryIndex, Tails.SOLO); + + bucket = default; + _tails.SetEmpty(entryIndex); + InsertEntry(entry, shifting: true); + + for (var i = 1; i < tailIndices.Length; i++) + { + var tailIndex = tailIndices[i]; + ref var tailBucket = ref _buckets[tailIndex]; + var tailEntry = tailBucket; + + tailBucket = default; + _tails.SetEmpty(tailIndex); + InsertEntry(tailEntry, shifting: true); + } + } + + private unsafe bool RemoveInternal(TKey key, out Entry removedEntry, bool checkValue = false, + TValue? value = default) + { + var bucketIndex = GetBucketIndex(key); + + while (true) + { + ref var entry = ref _buckets[bucketIndex]; + + if (_keyEqualityComparer(key, entry.Key)) + { + if (checkValue && !value.Equals(entry.Value)) + goto OnFailure; + + removedEntry = entry; + + var tailIndex = -1; + if (IsTail(ref entry, bucketIndex)) + { + if (HasTail(bucketIndex)) + tailIndex = GetTailIndex(bucketIndex); + else + SetParentTail(bucketIndex, Tails.SOLO); + } + + entry.Key = default!; + entry.Value = default; + _tails.SetEmpty(bucketIndex); + + if (tailIndex != -1) + EmplaceWithTails(tailIndex); + + return true; + } + + if (TryContinueWithTail(ref bucketIndex)) + continue; + + OnFailure: + Unsafe.SkipInit(out removedEntry); + return false; + } + } + + [CollectionAccess(CollectionAccessType.UpdatedContent)] + public void Add(KeyValuePair item) => Add(item.Key, item.Value); + + [CollectionAccess(CollectionAccessType.UpdatedContent)] + public void AddRange(IList> range) + { + Guard.IsNotNull(range); + + EnsureCapacity((int)(range.Count * 1.5f)); + for (var i = range.Count; i-- > 0;) + Add(range[i]); + } + + [CollectionAccess(CollectionAccessType.UpdatedContent)] + public void AddRange(IEnumerable> range) + { + Guard.IsNotNull(range); + + if (range is IList> iList) + AddRange(iList); + else + AddRangeFromEnumerable(range); + } + + private void AddRangeFromEnumerable(IEnumerable> range) + { + Guard.IsNotNull(range); + + foreach (var item in range) + Add(item); + } + + [CollectionAccess(CollectionAccessType.UpdatedContent)] + public void AddRange(IList range, Func keySelector, + Func valueSelector) + { + Guard.IsNotNull(range); + Guard.IsNotNull(keySelector); + Guard.IsNotNull(valueSelector); + + EnsureCapacity((int)(range.Count * 1.5f)); + for (var i = range.Count; i-- > 0;) + { + var item = range[i]; + Add(keySelector(item), valueSelector(item)); + } + } + + [CollectionAccess(CollectionAccessType.UpdatedContent)] + public void AddRange(IEnumerable range, Func keySelector, + Func valueSelector) + { + Guard.IsNotNull(range); + Guard.IsNotNull(keySelector); + Guard.IsNotNull(valueSelector); + + if (range is IList iList) + AddRange(iList, keySelector, valueSelector); + else + AddRangeFromEnumerable(range, keySelector, valueSelector); + } + + private void AddRangeFromEnumerable(IEnumerable range, Func keySelector, + Func valueSelector) + { + Guard.IsNotNull(range); + Guard.IsNotNull(keySelector); + Guard.IsNotNull(valueSelector); + + foreach (var item in range) + Add(keySelector(item), valueSelector(item)); + } + + [CollectionAccess(CollectionAccessType.UpdatedContent)] + public void AddRange(IList keys, IList values) + { + Guard.IsNotNull(keys); + Guard.IsNotNull(values); + + var count = keys.Count; + if (count != values.Count) + ThrowHelper.ThrowInvalidKeysValuesSizeArgumentException(); + + EnsureCapacity((int)(count * 1.5f)); + for (var i = count; i-- > 0;) + Add(keys[i], values[i]); + } + + [CollectionAccess(CollectionAccessType.UpdatedContent)] + public void AddRange(IEnumerable keys, IEnumerable values) + { + Guard.IsNotNull(keys); + Guard.IsNotNull(values); + + if (keys is IList keysList && values is IList valuesList) + AddRange(keysList, valuesList); + else + AddRangeFromEnumerable(keys, values); + } + + private void AddRangeFromEnumerable(IEnumerable keys, IEnumerable values) + { + Guard.IsNotNull(keys); + Guard.IsNotNull(values); + + using var keyEnumerator = keys.GetEnumerator(); + using var valueEnumerator = values.GetEnumerator(); + + while (keyEnumerator.MoveNext()) + { + if (!valueEnumerator.MoveNext()) + ThrowHelper.ThrowInvalidKeysValuesSizeArgumentException(); + + Add(keyEnumerator.Current, valueEnumerator.Current); + } + } + + [CollectionAccess(CollectionAccessType.ModifyExistingContent)] + public void Clear() + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (_buckets is null) + Initialize(); + + Array.Clear(_buckets, 0, _buckets.Length); + _tails.Reset(); + + _count = 0; + _version++; + } + + [CollectionAccess(CollectionAccessType.Read)] + public bool Contains(KeyValuePair item) + => TryGetValue(item.Key, out var value) + && value.Equals(item.Value); + + [CollectionAccess(CollectionAccessType.Read)] + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + var length = _buckets.Length; + for (var i = 0; i < length; i++) + { + if (IsBucketEmpty(i)) + continue; + + array[arrayIndex] = _buckets[i].AsKeyValuePair()!; + arrayIndex++; + } + } + + [CollectionAccess(CollectionAccessType.ModifyExistingContent)] + public bool Remove(KeyValuePair item) + { + if (!RemoveInternal(item.Key, out _, true, item.Value)) + return false; + + OnEntryRemoved(item); + return true; + } + + private void OnEntryRemoved(in KeyValuePair entry) + { + _version++; + _count--; + EntryRemoved?.Invoke(entry); + } + + #region InterfaceMethods + + [CollectionAccess(CollectionAccessType.Read)] + public Enumerator GetEnumerator() => new(this, Enumerator.KEY_VALUE_PAIR); + + IEnumerator> IEnumerable>.GetEnumerator() + => new Enumerator(this, Enumerator.KEY_VALUE_PAIR); + + IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this, Enumerator.KEY_VALUE_PAIR); + + bool IDictionary.Contains(object key) + { + Guard.IsNotNull(key); + + return key is TKey tKey && ContainsKey(tKey); + } + + void IDictionary.Add(object key, object value) => Add(AssertKeyType(key), AssertValueType(value)); + + IDictionaryEnumerator IDictionary.GetEnumerator() => new Enumerator(this, Enumerator.DICT_ENTRY); + + void IDictionary.Remove(object key) + { + Guard.IsNotNull(key); + + if (key is TKey tKey) + Remove(tKey); + } + + void ICollection.CopyTo(Array array, int index) + { + if (array is KeyValuePair[] typedArray) + { + CopyTo(typedArray, index); + return; + } + + var length = _buckets.Length; + for (var i = 0; i < length; i++) + { + if (IsBucketEmpty(i)) + continue; + + ((IList)array)[index] = _buckets[i].AsKeyValuePair(); + index++; + } + } + + #endregion + + #region Structs + + // [StructLayout(LayoutKind.Sequential, Pack = 1)] + // matches KeyValuePair and gets reinterpret cast into that in the Enumerator + internal struct Entry + { + public TKey Key; + public TValue? Value; + + public Entry(TKey key, TValue? value) + { + Key = key; + Value = value; + } + + [Pure] + [UnscopedRef] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [SecuritySafeCritical] + public ref KeyValuePair AsKeyValuePair() + => ref Unsafe.As>(ref this); + } + + internal struct Tails + { + public const uint + EMPTY = 0u, + SOLO = 1u; + + private NibbleArray _values; + + public uint Length => _values.Length; + + public Tails(uint length) => _values = new(length); + + public uint this[uint index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _values[index]; + + set => _values[index] = value; + } + + public void Reset() => _values.Clear(); //_values.Initialize(15u); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsEmpty(int index) => _values[(uint)index] == EMPTY; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsSolo(int index) => _values[(uint)index] == SOLO; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsSoloOrEmpty(int index) => _values[(uint)index] <= 1u; + + public void SetSolo(int index) => _values[(uint)index] = SOLO; + + public void SetEmpty(int index) => _values[(uint)index] = EMPTY; + } + + public record struct Enumerator : IEnumerator>, IDictionaryEnumerator + { + private readonly FishTable _dictionary; + private readonly int _version; + private uint _index; + private KeyValuePair _current; + private readonly int _getEnumeratorRetType; // What should Enumerator.Current return? + + internal const int DICT_ENTRY = 1; + internal const int KEY_VALUE_PAIR = 2; + + internal Enumerator(FishTable dictionary, int getEnumeratorRetType) + { + _dictionary = dictionary; + _version = dictionary._version; + _index = 0u; + _getEnumeratorRetType = getEnumeratorRetType; + _current = default; + } + + public bool MoveNext() + { + if (_version != _dictionary._version) + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); + + while (_index < (uint)_dictionary._buckets.Length) + { + if (_dictionary.IsBucketEmpty((int)_index++)) + continue; + + _current = _dictionary._buckets[_index - 1].AsKeyValuePair()!; + return true; + } + + _index = (uint)_dictionary._buckets.Length + 1u; + _current = default; + return false; + } + + [CollectionAccess(CollectionAccessType.Read)] + public KeyValuePair Current => _current; + + public void Dispose() + { + } + + object IEnumerator.Current + { + get + { + if (_index == 0u || _index == (uint)_dictionary._buckets.Length + 1u) + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); + + return _getEnumeratorRetType == DICT_ENTRY + ? new DictionaryEntry(_current.Key!, _current.Value) + : _current; + } + } + + void IEnumerator.Reset() + { + if (_version != _dictionary._version) + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); + + _index = 0u; + _current = default; + } + + DictionaryEntry IDictionaryEnumerator.Entry + { + get + { + if (_index == 0u || _index == (uint)_dictionary._buckets.Length + 1u) + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); + + return new(_current.Key!, _current.Value); + } + } + + object? IDictionaryEnumerator.Key + { + get + { + if (_index == 0u || _index == (uint)_dictionary._buckets.Length + 1u) + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); + + return _current.Key; + } + } + + object? IDictionaryEnumerator.Value + { + get + { + if (_index == 0u || _index == (uint)_dictionary._buckets.Length + 1u) + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); + + return _current.Value; + } + } + } + + #endregion + + private static class ThrowHelper + { + [DoesNotReturn] + internal static void ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion() + => throw new InvalidOperationException("Collection was modified; enumeration operation may not execute."); + + [DoesNotReturn] + internal static void ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen() + => throw new InvalidOperationException("Enumeration has either not started or has already finished."); + + [DoesNotReturn] + internal static void ThrowKeyNotFoundException(T key) + { + Guard.IsNotNull(key); + ThrowKeyNotFoundException((object)key); + } + + [DoesNotReturn] + private static void ThrowKeyNotFoundException(object key) + => throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary."); + + [DoesNotReturn] + internal static void ThrowWrongValueTypeArgumentException(object value) + => throw new ArgumentException( + $"The value \"{value}\" is not of type \"{ + typeof(TValue)}\" and cannot be used in this generic collection.", + nameof(value)); + + [DoesNotReturn] + internal static void ThrowWrongKeyTypeArgumentException(object? key) + { + Guard.IsNotNull(key); + throw new ArgumentException($"The value \"{key}\" is not of type \"{ + typeof(TKey)}\" and cannot be used in this generic collection.", + nameof(key)); + } + + [DoesNotReturn] + internal static void ThrowAddingDuplicateWithKeyArgumentException(T key) + { + Guard.IsNotNull(key); + ThrowAddingDuplicateWithKeyArgumentException((object)key); + } + + [DoesNotReturn] + internal static void ThrowEmptyBucketKeyInvalidOperationException(int bucketIndex, Entry bucket) + => throw new InvalidOperationException( + $"Bucket marked as empty had non-default key. This should never happen. Index: '{ + bucketIndex}', key: '{bucket.Key}', value: '{bucket.Value}'"); + + [DoesNotReturn] + internal static void ThrowInvalidKeysValuesSizeArgumentException() + => Utility.Diagnostics.ThrowHelper.ThrowArgumentException( + "Input collections have different sizes. AddRange expects a matching Count for keys and values"); + + [DoesNotReturn] + internal static unsafe void ThrowFailedToFindParentInvalidOperationException(FishTable fishTable, + int childBucketIndex) + { + var entry = fishTable._buckets[childBucketIndex]; + throw new InvalidOperationException($"Failed to find parent index in FishTable for key: '{ + entry.Key}', hashCode: '{fishTable._keyHashCodeGetter(entry.Key)}', value: '{entry.Value}', count: '{ + fishTable._count}', bucket array length: '{fishTable._buckets.Length}', tailing entries: '{ + fishTable.GetTailingEntriesCount()}'"); + } + + [DoesNotReturn] + private static void ThrowAddingDuplicateWithKeyArgumentException(object key) + => throw new ArgumentException($"An item with the same key has already been added. Key: {key}"); + } +} +#pragma warning restore CS8766, CS8767 \ No newline at end of file diff --git a/Source/FisheryLib/Collections/NibbleArray.cs b/Source/FisheryLib/Collections/NibbleArray.cs new file mode 100644 index 0000000..4edabf7 --- /dev/null +++ b/Source/FisheryLib/Collections/NibbleArray.cs @@ -0,0 +1,62 @@ +// Copyright (c) 2023 bradson +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace FisheryLib.Collections; + +[PublicAPI] +public struct NibbleArray +{ + private uint _length; + private byte[] _data; + + public uint this[uint index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Guard.IsLessThan(index, _length); + + return (index & 1u) == 0u ? _data[index >> 1] & 0b0000_1111u : (uint)_data[index >> 1] >> 4; + } + set + { + Guard.IsLessThan(value, 16u); + Guard.IsLessThan(index, _length); + + _data[index >> 1] = (byte)((index & 1u) == 0u + ? (_data[index >> 1] & 0b1111_0000u) | value + : (_data[index >> 1] & 0b0000_1111u) | (value << 4)); + } + } + + public uint Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _length; + } + + public NibbleArray(uint length) + { + _length = length; + _data = new byte[(length + 1u) >> 1]; + } + + public void Resize(uint newSize) + { + _length = newSize; + Array.Resize(ref _data, (int)(newSize + 1u) >> 1); + } + + public void Clear() => _data.Clear(); + + public void Initialize(uint value) + { + Guard.IsLessThan(value, 16u); + Unsafe.InitBlockUnaligned(ref _data[0u], (byte)(value | (value << 4)), (uint)_data.Length); + } +} \ No newline at end of file diff --git a/Source/FisheryLib/Collections/StructFishTable.cs b/Source/FisheryLib/Collections/StructFishTable.cs new file mode 100644 index 0000000..d6cd185 --- /dev/null +++ b/Source/FisheryLib/Collections/StructFishTable.cs @@ -0,0 +1,1050 @@ +// Copyright (c) 2023 bradson +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// +// Based on https://github.com/matthewcrews/FastDictionaryTest/tree/main +// which itself is based on +// https://probablydance.com/2018/05/28/a-new-fast-hash-table-in-response-to-googles-new-fast-hash-table/ + +#if false + +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using FisheryLib.FunctionPointers; +using JetBrains.Annotations; + +namespace FisheryLib.Collections; + +#pragma warning disable CS8766, CS8767 +[PublicAPI] +[StructLayout(LayoutKind.Sequential)] +public struct StructFishTable : ICollection + //: IDictionary, IDictionary, IReadOnlyDictionary +{ + private static ReadOnlySpan TrailingZeroCountDeBruijn + => new byte[] + { + 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, + 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, + 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, + 10, 9 + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int TrailingZeroCount(uint value) + => value == 0 + ? 32 + : Unsafe.AddByteOffset(ref TrailingZeroCountDeBruijn.DangerousGetPinnableReference(), + (IntPtr)(int)(((value & unchecked(0 - value)) * 125613361) >> 27)); + + internal Entry[] _buckets; + + private unsafe delegate* _keyHashCodeByRefGetter = GetHashCodeByRef.Default; + private unsafe delegate* _keyEqualityByRefComparer = EqualsByRef.Default; + + private int + _bucketBitShift, + _count; + + private unsafe delegate* _keyHashCodeGetter = GetHashCode.Default; + private unsafe delegate* _keyEqualityComparer = Equals.Default; + + private int + _wrapAroundMask, + _version; + + //private KeyCollection _keys; + + //private ValueCollection _values; + + public ICollection Keys => throw new NotImplementedException(); + + public ICollection Values => throw new NotImplementedException(); + + public int Version + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _version; + } + + public int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _count; + } + + public bool IsReadOnly => false; + + // ICollection IDictionary.Keys => throw new NotImplementedException(); + // + // ICollection IDictionary.Values => throw new NotImplementedException(); + // + // bool IDictionary.IsFixedSize => false; + object ICollection.SyncRoot => this; // matching System.Collections.Generic.Dictionary + bool ICollection.IsSynchronized => false; + // + // IEnumerable IReadOnlyDictionary.Keys => throw new NotImplementedException(); + // + // IEnumerable IReadOnlyDictionary.Values => throw new NotImplementedException(); + // + // object? IDictionary.this[object key] + // { + // get + // => key is TKey tKey + // ? this[tKey] + // : ThrowHelper.ThrowKeyNotFoundException(key); + // + // set + // { + // if (key is TKey tKey) + // { + // switch (value) + // { + // case TValue tValue: + // this[tKey] = tValue; + // break; + // case null: + // this[tKey] = default; + // break; + // default: + // ThrowHelper.ThrowWrongValueTypeArgumentException(value); + // break; + // } + // } + // else + // { + // ThrowHelper.ThrowWrongKeyTypeArgumentException(key); + // } + // } + // } + + public unsafe TValue? this[TKey key] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + var hashCode = _keyHashCodeGetter(key); + var entryIndex = ComputeBucketIndex(hashCode); + + while (true) + { + ref var entry = ref _buckets[entryIndex]; + + if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) + return entry.Value; + + if (entry.IsLast) + ThrowHelper.ThrowKeyNotFoundException(key); + + SetNextEntryIndex(ref entryIndex, entry.Next); + } + } + set + { + AddEntry(key, value); + Resize(); + } + } + + public StructFishTable() : this(0) + { + } + + public StructFishTable(int minimumCapacity) => Initialize(minimumCapacity); + + [MemberNotNull(nameof(_buckets))] + private void Initialize(int minimumCapacity = 0) + { + //_buckets = new Entry[minimumCapacity <= 1 ? 1 : Mathf.NextPowerOfTwo(minimumCapacity)]; + + _buckets = new Entry[4]; + for (var i = 0; i < _buckets.Length; i++) + _buckets[i].Next = byte.MaxValue; + + _bucketBitShift = 32 - TrailingZeroCount((uint)_buckets.Length); + _wrapAroundMask = _buckets.Length - 1; + } + + public StructFishTable(IEnumerable> entries) : this(0) + { + foreach (var entry in entries) + { + AddEntry(entry.Key, entry.Value); + Resize(); + } + } + + private void SetNextEntryIndex(ref int entryIndex, byte next) => entryIndex = GetNextEntryIndex(entryIndex, next); + + private int GetNextEntryIndex(int entryIndex, byte next) => (entryIndex + next /*GetJumpDistance(next)*/) & _wrapAroundMask; + + // private static long GetJumpDistanceLong(byte index) + // => index switch + // { + // 0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7, + // 8 => 8, 9 => 9, 10 => 10, 11 => 11, 12 => 12, 13 => 13, 14 => 14, + // 15 => 15, 16 => 21, 17 => 28, 18 => 36, 19 => 45, 20 => 55, 21 => 66, + // 22 => 78, 23 => 91, 24 => 105, 25 => 120, 26 => 136, 27 => 153, + // 28 => 171, 29 => 190, 30 => 210, 31 => 231, 32 => 253, 33 => 276, + // 34 => 300, 35 => 325, 36 => 351, 37 => 378, 38 => 406, 39 => 435, + // 40 => 465, 41 => 496, 42 => 528, 43 => 561, 44 => 595, 45 => 630, + // 46 => 666, 47 => 703, 48 => 741, 49 => 780, 50 => 820, 51 => 861, + // 52 => 903, 53 => 946, 54 => 990, 55 => 1035, 56 => 1081, 57 => 1128, + // 58 => 1176, 59 => 1225, 60 => 1275, 61 => 1326, 62 => 1378, 63 => 1431, + // 64 => 1485, 65 => 1540, 66 => 1596, 67 => 1653, 68 => 1711, 69 => 1770, + // 70 => 1830, 71 => 1891, 72 => 1953, 73 => 2016, 74 => 2080, 75 => 2145, + // 76 => 2211, 77 => 2278, 78 => 2346, 79 => 2415, 80 => 2485, 81 => 2556, + // 82 => 3741, 83 => 8385, 84 => 18915, 85 => 42486, 86 => 95703, 87 => 215496, + // 88 => 485605, 89 => 1091503, 90 => 2456436, 91 => 5529475, 92 => 12437578, + // 93 => 27986421, 94 => 62972253, 95 => 141700195, 96 => 318819126, 97 => 717314626, + // 98 => 1614000520, 99 => 3631437253, 100 => 8170829695, 101 => 18384318876, 102 => 41364501751, + // 103 => 93070021080, 104 => 209407709220, 105 => 471167588430, 106 => 1060127437995, 107 => 2385287281530, + // 108 => 5366895564381, 109 => 12075513791265, 110 => 27169907873235, 111 => 61132301007778, + // 112 => 137547673121001, 113 => 309482258302503, 114 => 696335090510256, 115 => 1566753939653640, + // 116 => 3525196427195653, 117 => 7931691866727775, 118 => 17846306747368716, + // 119 => 40154190394120111, 120 => 90346928493040500, 121 => 203280588949935750, + // 122 => 457381324898247375, 123 => 1029107980662394500, 124 => 2315492957028380766, + // 125 => 5209859150892887590, > 125 => throw new() + // }; + + // private static int GetJumpDistance(byte index) + // => index switch + // { + // 0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7, 8 => 8, 9 => 9, 10 => 10, 11 => 11, + // 12 => 12, 13 => 13, 14 => 14, 15 => 15, 16 => 21, 17 => 28, 18 => 36, 19 => 45, 20 => 55, 21 => 66, + // 22 => 78, 23 => 91, 24 => 105, 25 => 120, 26 => 136, 27 => 153, 28 => 171, 29 => 190, 30 => 210, 31 => 231, + // 32 => 253, 33 => 276, 34 => 300, 35 => 325, 36 => 351, 37 => 378, 38 => 406, 39 => 435, 40 => 465, + // 41 => 496, 42 => 528, 43 => 561, 44 => 595, 45 => 630, 46 => 666, 47 => 703, 48 => 741, 49 => 780, + // 50 => 820, 51 => 861, 52 => 903, 53 => 946, 54 => 990, 55 => 1035, 56 => 1081, 57 => 1128, 58 => 1176, + // 59 => 1225, 60 => 1275, 61 => 1326, 62 => 1378, 63 => 1431, 64 => 1485, 65 => 1540, 66 => 1596, 67 => 1653, + // 68 => 1711, 69 => 1770, 70 => 1830, 71 => 1891, 72 => 1953, 73 => 2016, 74 => 2080, 75 => 2145, 76 => 2211, + // 77 => 2278, 78 => 2346, 79 => 2415, 80 => 2485, 81 => 2556, 82 => 3741, 83 => 8385, 84 => 18915, + // 85 => 42486, 86 => 95703, 87 => 215496, 88 => 485605, 89 => 1091503, 90 => 2456436, 91 => 5529475, + // 92 => 12437578, 93 => 27986421, 94 => 62972253, 95 => 141700195, 96 => 318819126, 97 => 717314626, + // 98 => 1614000520, 255 => int.MaxValue, > 98 => ThrowForInvalidIndex(index) + // }; + + private static int ThrowForInvalidIndex(byte index) + => throw new($"Jump distance index for FishTable too large. Should be < 99, got {index} instead."); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ComputeBucketIndex(int hashCode) => (hashCode * -1640531527) >>> _bucketBitShift; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe bool IsTail(int bucketIndex) + => bucketIndex != ComputeBucketIndex(_keyHashCodeGetter(_buckets[bucketIndex].Key)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int GetParentBucketIndex(int hashCode, int childBucketIndex) + { + var ancestorIndex = ComputeBucketIndex(hashCode); + while (true) + { + var nextIndex = GetNextEntryIndex(ancestorIndex, _buckets[ancestorIndex].Next); + if (nextIndex == childBucketIndex) + break; + + ancestorIndex = nextIndex; + } + return ancestorIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe byte DistanceFromParent(int bucketIndex) + => _buckets[GetParentBucketIndex(_keyHashCodeGetter(_buckets[bucketIndex].Key), bucketIndex)].Next; + + private void SetEntry(byte next, int hashCode, TKey key, TValue? value, int bucketIndex) + { + ref var entry = ref _buckets[bucketIndex]; + entry.Next = next; + entry.Key = key; + entry.Value = value; + } + + private unsafe void RemoveFromList(int entryIndex) + { + ref var entry = ref _buckets[entryIndex]; + var parentEntryIndex = GetParentBucketIndex(_keyHashCodeGetter(entry.Key), entryIndex); + + _buckets[parentEntryIndex].Next + = entry.IsLast ? (byte)0 : (byte)(_buckets[parentEntryIndex].Next + entry.Next); + } + + private unsafe void AddEntry(TKey key, TValue? value) => AddEntry(_keyHashCodeGetter(key), key, value); + + private unsafe void AddEntry(int hashCode, TKey key, TValue? value) + { + _version++; + + var entryIndex = ComputeBucketIndex(hashCode); + ref var entry = ref _buckets[entryIndex]; + if (entry.IsAvailable) + { + SetEntry(0, hashCode, key, value, entryIndex); + _count++; + } + else if (_keyHashCodeGetter(entry.Key) == hashCode && _keyEqualityComparer(entry.Key, key)) + { + entry.Value = value; + } + else if (IsTail(entryIndex)) + { + var previousEntry = _buckets[entryIndex]; + RemoveFromList(entryIndex); + SetEntry(0, hashCode, key, value, entryIndex); + AddEntry(_keyHashCodeGetter(previousEntry.Key), previousEntry.Key, previousEntry.Value); + _count++; + } + else + { + ListSearch(hashCode, key, value, entryIndex); + } + } + + private unsafe void ListSearch(int hashCode, TKey key, TValue? value, int bucketIndex) + { + while (true) + { + ref var bucket = ref _buckets[bucketIndex]; + if (_keyHashCodeGetter(bucket.Key) == hashCode && _keyEqualityComparer(bucket.Key, key)) + { + bucket.Value = value; + return; + } + if (bucket.IsLast | bucket.IsEmpty) + break; + + SetNextEntryIndex(ref bucketIndex, bucket.Next); + } + InsertIntoNextEmptyBucket(bucketIndex, hashCode, key, value, 1, bucketIndex + 1); + } + + private unsafe void InsertIntoNextEmptyBucket(int parentIndex, int hashCode, TKey key, TValue? value, byte offset, int bucketIndex) + { + while (true) + { + if (bucketIndex < _buckets.Length) + { + if (_buckets[bucketIndex].IsAvailable) + { + SetEntry(0, hashCode, key, value, bucketIndex); + _count++; + _buckets[parentIndex].Next = offset; + return; + } + if (IsTail(bucketIndex) && (uint)offset > DistanceFromParent(bucketIndex)) + break; + + bucketIndex++; + offset++; + } + else + { + bucketIndex = 0; + } + } + var previousEntry = _buckets[bucketIndex]; + RemoveFromList(bucketIndex); + SetEntry(0, hashCode, key, value, bucketIndex); + _buckets[parentIndex].Next = offset; + AddEntry(_keyHashCodeGetter(previousEntry.Key), previousEntry.Key, previousEntry.Value); + } + + private void Resize() + { + if (_count <= (_buckets.Length >> 2) * 3) + return; + var oldBuckets = _buckets; + + _buckets = new Entry[_buckets.Length << 1]; + for (var i = 0; i < _buckets.Length; i++) + _buckets[i].Next = byte.MaxValue; + + _bucketBitShift = 64 - TrailingZeroCount((uint)_buckets.Length); + _wrapAroundMask = _buckets.Length - 1; + _count = 0; + for (var i = 0; i < oldBuckets.Length; i++) + { + var bucket = oldBuckets[i]; + if (bucket.IsEntry) + AddEntry(bucket.Key, bucket.Value); + } + } + + public unsafe bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + { + var hashCode = _keyHashCodeGetter(key); + var entryIndex = ComputeBucketIndex(hashCode); + + while (true) + { + ref var entry = ref _buckets[entryIndex]; + + if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) + { + value = entry.Value!; + return true; + } + + if (entry.IsLast | entry.IsEmpty) + { + value = default; + return false; + } + + SetNextEntryIndex(ref entryIndex, entry.Next); + } + } + + public unsafe TValue? TryGetValue(TKey key) + { + var hashCode = _keyHashCodeGetter(key); + var entryIndex = ComputeBucketIndex(hashCode); + + while (true) + { + ref var entry = ref _buckets[entryIndex]; + + if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) + return entry.Value; + + if (entry.IsLast | entry.IsEmpty) + return default; + + SetNextEntryIndex(ref entryIndex, entry.Next); + } + } + + public unsafe TValue GetOrAdd(TKey key) + { + while (true) + { + var hashCode = _keyHashCodeGetter(key); + var entryIndex = ComputeBucketIndex(hashCode); + + while (true) + { + ref var entry = ref _buckets[entryIndex]; + + if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) + return entry.Value!; + + if (entry.IsLast | entry.IsEmpty) + { + this[key] = Reflection.New(); + break; + } + + SetNextEntryIndex(ref entryIndex, entry.Next); + } + } + } + + public unsafe TValue GetOrAdd(ref TKey key) + { + while (true) + { + var hashCode = _keyHashCodeByRefGetter(ref key); + var entryIndex = ComputeBucketIndex(hashCode); + + while (true) + { + ref var entry = ref _buckets[entryIndex]; + + if ((hashCode == _keyHashCodeByRefGetter(ref entry.Key)) & _keyEqualityByRefComparer(ref key, ref entry.Key)) + return entry.Value!; + + if (entry.IsLast | entry.IsEmpty) + { + this[key] = Reflection.New(); + break; + } + + SetNextEntryIndex(ref entryIndex, entry.Next); + } + } + } + + public unsafe ref TValue GetOrAddReference(TKey key) + { + while (true) + { + var hashCode = _keyHashCodeGetter(key); + var entryIndex = ComputeBucketIndex(hashCode); + + while (true) + { + ref var entry = ref _buckets[entryIndex]; + + if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) + return ref entry.Value!; + + if (entry.IsLast | entry.IsEmpty) + { + this[key] = Reflection.New(); + break; + } + + SetNextEntryIndex(ref entryIndex, entry.Next); + } + } + } + + public unsafe ref TValue GetOrAddReference(ref TKey key) + { + while (true) + { + var hashCode = _keyHashCodeByRefGetter(ref key); + var entryIndex = ComputeBucketIndex(hashCode); + + while (true) + { + ref var entry = ref _buckets[entryIndex]; + + if ((hashCode == _keyHashCodeByRefGetter(ref entry.Key)) + & _keyEqualityByRefComparer(ref key, ref entry.Key)) + { + return ref entry.Value!; + } + + if (entry.IsLast | entry.IsEmpty) + { + this[key] = Reflection.New(); + break; + } + + SetNextEntryIndex(ref entryIndex, entry.Next); + } + } + } + + public unsafe ref TValue GetReference(TKey key) + { + var hashCode = _keyHashCodeGetter(key); + var entryIndex = ComputeBucketIndex(hashCode); + + while (true) + { + ref var entry = ref _buckets[entryIndex]; + + if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) + return ref entry.Value!; + + if (entry.IsLast | entry.IsEmpty) + ThrowHelper.ThrowKeyNotFoundException(key); + + SetNextEntryIndex(ref entryIndex, entry.Next); + } + } + + public unsafe ref TValue GetReference(ref TKey key) + { + var hashCode = _keyHashCodeByRefGetter(ref key); + var entryIndex = ComputeBucketIndex(hashCode); + + while (true) + { + ref var entry = ref _buckets[entryIndex]; + + if ((hashCode == _keyHashCodeByRefGetter(ref entry.Key)) & _keyEqualityByRefComparer(ref key, ref entry.Key)) + return ref entry.Value!; + + if (entry.IsLast | entry.IsEmpty) + ThrowHelper.ThrowKeyNotFoundException(key); + + SetNextEntryIndex(ref entryIndex, entry.Next); + } + } + + /// + /// Returns a reference to the value field of an entry if the key exists and an Unsafe.NullRef{TValue} if not. + /// Must be checked with Unsafe.IsNullRef before assigning to a regular var or field without ref. + /// + /// The key + /// ref TValue or Unsafe.NullRef{TValue} + public unsafe ref TValue? TryGetReferenceUnsafe(TKey key) + { + var hashCode = _keyHashCodeGetter(key); + var entryIndex = ComputeBucketIndex(hashCode); + + while (true) + { + ref var entry = ref _buckets[entryIndex]; + + if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) + return ref entry.Value; + + if (entry.IsLast | entry.IsEmpty) + return ref Unsafe.NullRef(); + + SetNextEntryIndex(ref entryIndex, entry.Next); + } + } + + public unsafe ref TValue? TryGetReferenceUnsafe(ref TKey key) + { + var hashCode = _keyHashCodeByRefGetter(ref key); + var entryIndex = ComputeBucketIndex(hashCode); + + while (true) + { + ref var entry = ref _buckets[entryIndex]; + + if ((hashCode == _keyHashCodeByRefGetter(ref entry.Key)) + & _keyEqualityByRefComparer(ref key, ref entry.Key)) + { + return ref entry.Value; + } + + if (entry.IsLast | entry.IsEmpty) + return ref Unsafe.NullRef(); + + SetNextEntryIndex(ref entryIndex, entry.Next); + } + } + + public unsafe bool ContainsKey(TKey key) + { + var hashCode = _keyHashCodeGetter(key); + var entryIndex = ComputeBucketIndex(hashCode); + + while (true) + { + ref var entry = ref _buckets[entryIndex]; + + if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) + return true; + + if (entry.IsLast | entry.IsEmpty) + return false; + + SetNextEntryIndex(ref entryIndex, entry.Next); + } + } + +#if false + public void Add(TKey key, TValue value) + { + Guard.IsNotNull(key); + + if ( + _count + == _buckets.Length) // throwing DuplicateKeyExceptions happens later, so Expanding occurs based on expected + Expand(); // behaviour instead of matching actual results like in System.Collections.Generic. + + var keyCode = (uint)HashCode.Get(key); + ref var entry = ref _buckets[FastModulo(keyCode, (uint)_buckets.Length)]; + + while (entry.Next != null) + { + if (entry.HashCode == keyCode && entry.Key.Equals(key)) + ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key); + + if (entry.Next == EntryReference.Empty) + break; + + entry = ref entry.Next.Entry; + } + + if (entry.Next == null) // means the entry is empty + entry = new(keyCode, key, value); + else // if (entry.Next == _freeSlot) // occupied entry with an empty slot in entry.Next + entry.Next = new(keyCode, key, value); + // entry.Next != _freeSlot would be a threading issue, as this was just checked in the loop. Unhandled. + + _count++; + _version++; + } + + private void Expand() + { + var newLength = Mathf.NextPowerOfTwo(_buckets.Length + 1); + var newArray = new Entry[newLength]; + + for (var i = 0; i < _buckets.Length; i++) + { + var entry = _buckets[i]; + + var nextEntry = entry.Next; + if (nextEntry is null) + continue; + + BeforeAdding: + ref var newEntry = ref newArray[FastModulo(entry.HashCode, (uint)newLength)]; + + while (newEntry.Next != null) + { + if (newEntry.HashCode == entry.HashCode && newEntry.Key.Equals(entry.Key)) + ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(entry.Key); + + if (newEntry.Next == EntryReference.Empty) + break; + + newEntry = ref newEntry.Next.Entry; + } + + entry.Next = EntryReference.Empty; + + if (newEntry.Next == null) + newEntry = entry; + else + newEntry.Next = new(entry); + + if (nextEntry != EntryReference.Empty) + { + entry = nextEntry.Entry; + goto BeforeAdding; + } + } + + _buckets = newArray; + } + + private ref Entry TryFindEntry(TKey key) + { + var keyCode = (uint)HashCode.Get(key); + ref var entry = ref _buckets[FastModulo(keyCode, (uint)_buckets.Length)]; + + while (entry.HashCode != keyCode || !entry.Key.Equals(key)) + { + if (entry.Next != EntryReference.Empty && entry.Next != null) + entry = ref entry.Next.Entry; + else + return ref Unsafe.NullRef(); + } + + return ref entry; + } + + private void RemoveEntry(ref Entry entry) + { + entry = entry.Next != EntryReference.Empty && entry.Next != null + ? entry.Next.Entry + : default; + + _count--; + _version++; + } + + public bool Remove(TKey key) + { + ref var entry = ref TryFindEntry(key); + if (Unsafe.IsNullRef(ref entry)) + return false; + + RemoveEntry(ref entry); + return true; + } + + public void Add(KeyValuePair item) => Add(item.Key, item.Value); +#endif + + public void Clear() + { + if (_buckets is null) + Initialize(); + + Array.Clear(_buckets, 0, _buckets.Length); + for (var i = 0; i < _buckets.Length; i++) + _buckets[i].Next = byte.MaxValue; + + _count = 0; + _version++; + } + + public bool Contains(KeyValuePair item) + => TryGetValue(item.Key, out var value) + && value.Equals(item.Value); + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + var length = _buckets.Length; + for (var i = 0; i < length; i++) + { + ref var entry = ref _buckets[i]; + if (entry.IsEmpty) + continue; + + array[arrayIndex] = new(entry.Key, entry.Value!); + arrayIndex++; + } + } + +#if false + public bool Remove(KeyValuePair item) + { + ref var entry = ref TryFindEntry(item.Key); + + if (Unsafe.IsNullRef(ref entry) + || !entry.Value.Equals(item.Value)) + { + return false; + } + + RemoveEntry(ref entry); + return true; + } +#endif + + public Enumerator GetEnumerator() => new(this, Enumerator.KEY_VALUE_PAIR); + + // IEnumerator> IEnumerable>.GetEnumerator() + // => new Enumerator(this, Enumerator.KEY_VALUE_PAIR); + + IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this, Enumerator.KEY_VALUE_PAIR); + +#if false + bool IDictionary.Contains(object key) + { + Guard.IsNotNull(key); + + return key is TKey tkey + && ContainsKey(tkey); + } + + void IDictionary.Add(object key, object value) => throw new NotImplementedException(); + + //IDictionaryEnumerator IDictionary.GetEnumerator() => new Enumerator(this, Enumerator.DICT_ENTRY); + + void IDictionary.Remove(object key) + { + Guard.IsNotNull(key); + + if (key is TKey tkey) + Remove(tkey); + } +#endif + + void ICollection.CopyTo(Array array, int index) + { + if (array is KeyValuePair[] typedArray) + { + CopyTo(typedArray, index); + return; + } + + var length = _buckets.Length; + for (var i = 0; i < length; i++) + { + ref var entry = ref _buckets[i]; + if (entry.IsEmpty) + continue; + + ((IList)array)[index] = new KeyValuePair(entry.Key, entry.Value!); + index++; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct Entry + { + public byte Next; + public TKey Key; + public TValue? Value; + + internal bool IsTombstone + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Next == 254; + } + + internal bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Next == byte.MaxValue; + } + + internal bool IsEntry + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Next < 254u; + } + + internal bool IsOccupied + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Next <= 254u; + } + + internal bool IsAvailable + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Next >= 254u; + } + + internal bool IsLast + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Next == 0; + } + } + + public record struct Enumerator : IEnumerator>, IDictionaryEnumerator + { + private readonly StructFishTable _dictionary; + private readonly int _version; + private int _index; + private KeyValuePair _current; + private readonly int _getEnumeratorRetType; // What should Enumerator.Current return? + + internal const int DICT_ENTRY = 1; + internal const int KEY_VALUE_PAIR = 2; + + internal Enumerator(StructFishTable dictionary, int getEnumeratorRetType) + { + _dictionary = dictionary; + _version = dictionary._version; + _index = 0; + _getEnumeratorRetType = getEnumeratorRetType; + _current = default; + } + + public bool MoveNext() + { + if (_version != _dictionary._version) + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); + + // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends. + // dictionary.count+1 could be negative if dictionary.count is int.MaxValue + while ((uint)_index < (uint)_dictionary._buckets.Length) + { + ref var entry = ref _dictionary._buckets[_index++]; + + if (entry.IsEmpty) + continue; + + _current = new(entry.Key, entry.Value!); + return true; + } + + _index = _dictionary._buckets.Length + 1; + _current = default; + return false; + } + + public KeyValuePair Current => _current; + + public void Dispose() + { + } + + object IEnumerator.Current + { + get + { + if (_index == 0 || _index == _dictionary._buckets.Length + 1) + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); + + return _getEnumeratorRetType == DICT_ENTRY + ? new DictionaryEntry(_current.Key!, _current.Value) + : new KeyValuePair(_current.Key, _current.Value); + } + } + + void IEnumerator.Reset() + { + if (_version != _dictionary._version) + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); + + _index = 0; + _current = default; + } + + DictionaryEntry IDictionaryEnumerator.Entry + { + get + { + if (_index == 0 || _index == _dictionary._buckets.Length + 1) + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); + + return new(_current.Key!, _current.Value); + } + } + + object? IDictionaryEnumerator.Key + { + get + { + if (_index == 0 || _index == _dictionary._buckets.Length + 1) + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); + + return _current.Key; + } + } + + object? IDictionaryEnumerator.Value + { + get + { + if (_index == 0 || _index == _dictionary._buckets.Length + 1) + ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); + + return _current.Value; + } + } + } + + private static class ThrowHelper + { + [DoesNotReturn] + internal static void ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion() + => throw new InvalidOperationException("Collection was modified; enumeration operation may not execute."); + + [DoesNotReturn] + internal static void ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen() + => throw new InvalidOperationException("Enumeration has either not started or has already finished."); + + [DoesNotReturn] + internal static void ThrowKeyNotFoundException(T key) + { + Guard.IsNotNull(key); + ThrowKeyNotFoundException((object)key); + } + + [DoesNotReturn] + internal static V ThrowKeyNotFoundException(T key) + { + Guard.IsNotNull(key); + ThrowKeyNotFoundException((object)key); + return default; + } + + [DoesNotReturn] + private static void ThrowKeyNotFoundException(object key) + => throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary."); + + [DoesNotReturn] + internal static void ThrowWrongValueTypeArgumentException(object value) + => throw new ArgumentException( + $"The value \"{value}\" is not of type \"{ + typeof(TValue)}\" and cannot be used in this generic collection.", + nameof(value)); + + [DoesNotReturn] + internal static void ThrowWrongKeyTypeArgumentException(object? key) + { + Guard.IsNotNull(key); + throw new ArgumentException($"The value \"{key}\" is not of type \"{ + typeof(TKey)}\" and cannot be used in this generic collection.", + nameof(key)); + } + + [DoesNotReturn] + internal static void ThrowAddingDuplicateWithKeyArgumentException(T key) + { + Guard.IsNotNull(key); + ThrowAddingDuplicateWithKeyArgumentException((object)key); + } + + [DoesNotReturn] + private static void ThrowAddingDuplicateWithKeyArgumentException(object key) + => throw new ArgumentException($"An item with the same key has already been added. Key: {key}"); + } +} + +#pragma warning restore CS8766, CS8767 + +#endif \ No newline at end of file diff --git a/Source/FisheryLib/Convert.cs b/Source/FisheryLib/Convert.cs index fe164e9..0c5f9f0 100644 --- a/Source/FisheryLib/Convert.cs +++ b/Source/FisheryLib/Convert.cs @@ -7,23 +7,23 @@ using System.Globalization; using System.Runtime.CompilerServices; namespace FisheryLib; + public static class Convert { [Obsolete("Renamed to Convert.Type for clarity")] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe TTo To(TFrom from) where TFrom : IConvertible - => Type(from); + public static TTo To(TFrom from) => Type(from); [Obsolete("Renamed to Convert.Type for clarity")] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe TTo To(TFrom from, IFormatProvider provider) where TFrom : IConvertible + public static TTo To(TFrom from, IFormatProvider provider) => Type(from, provider); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe TTo Type(TFrom from) where TFrom : IConvertible + public static unsafe TTo Type(TFrom from) => FunctionPointers.Convert.To.Default(from, CultureInfo.InvariantCulture); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe TTo Type(TFrom from, IFormatProvider provider) where TFrom : IConvertible + public static unsafe TTo Type(TFrom from, IFormatProvider provider) => FunctionPointers.Convert.To.Default(from, provider); } \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Attributes/UnscopedRefAttribute.cs b/Source/FisheryLib/Diagnostics/Attributes/UnscopedRefAttribute.cs new file mode 100644 index 0000000..3665d18 --- /dev/null +++ b/Source/FisheryLib/Diagnostics/Attributes/UnscopedRefAttribute.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Used to indicate a byref escapes and is not scoped. +/// +/// +/// +/// There are several cases where the C# compiler treats a as implicitly +/// - where the compiler does not allow the to escape the method. +/// +/// +/// For example: +/// +/// for instance methods. +/// parameters that refer to types. +/// parameters. +/// +/// +/// +/// This attribute is used in those instances where the should be allowed to escape. +/// +/// +/// Applying this attribute, in any form, has impact on consumers of the applicable API. It is necessary for +/// API authors to understand the lifetime implications of applying this attribute and how it may impact their users. +/// +/// +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, + Inherited = false)] +internal sealed class UnscopedRefAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + public UnscopedRefAttribute() { } +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Extensions/TypeExtensions.cs b/Source/FisheryLib/Diagnostics/Extensions/TypeExtensions.cs index aacaa23..ee06ed7 100644 --- a/Source/FisheryLib/Diagnostics/Extensions/TypeExtensions.cs +++ b/Source/FisheryLib/Diagnostics/Extensions/TypeExtensions.cs @@ -52,9 +52,7 @@ public static class TypeExtensions { // Primitive types use the keyword name if (BuiltInTypesMap.TryGetValue(type, out var typeName)) - { return typeName!; - } // Array types are displayed as Foo[] if (type.IsArray) @@ -62,7 +60,8 @@ public static class TypeExtensions var elementType = type.GetElementType()!; var rank = type.GetArrayRank(); - return $"{FormatDisplayString(elementType, 0, elementType.GetGenericArguments())}[{new string(',', rank - 1)}]"; + return $"{FormatDisplayString(elementType, 0, + elementType.GetGenericArguments())}[{new string(',', rank - 1)}]"; } // By checking generic types here we are only interested in specific cases, @@ -84,16 +83,17 @@ public static class TypeExtensions } // ValueTuple types are displayed as (T1, T2) - if (genericTypeDefinition == typeof(ValueTuple<>) || - genericTypeDefinition == typeof(ValueTuple<,>) || - genericTypeDefinition == typeof(ValueTuple<,,>) || - genericTypeDefinition == typeof(ValueTuple<,,,>) || - genericTypeDefinition == typeof(ValueTuple<,,,,>) || - genericTypeDefinition == typeof(ValueTuple<,,,,,>) || - genericTypeDefinition == typeof(ValueTuple<,,,,,,>) || - genericTypeDefinition == typeof(ValueTuple<,,,,,,,>)) + if (genericTypeDefinition == typeof(ValueTuple<>) + || genericTypeDefinition == typeof(ValueTuple<,>) + || genericTypeDefinition == typeof(ValueTuple<,,>) + || genericTypeDefinition == typeof(ValueTuple<,,,>) + || genericTypeDefinition == typeof(ValueTuple<,,,,>) + || genericTypeDefinition == typeof(ValueTuple<,,,,,>) + || genericTypeDefinition == typeof(ValueTuple<,,,,,,>) + || genericTypeDefinition == typeof(ValueTuple<,,,,,,,>)) { - var formattedTypes = type.GetGenericArguments().Select(t => FormatDisplayString(t, 0, t.GetGenericArguments())); + var formattedTypes = type.GetGenericArguments() + .Select(static t => FormatDisplayString(t, 0, t.GetGenericArguments())); return $"({string.Join(", ", formattedTypes)})"; } @@ -109,7 +109,8 @@ public static class TypeExtensions var genericArgumentsCount = int.Parse(tokens[1]); var typeArgumentsOffset = typeArguments.Length - genericTypeOffset - genericArgumentsCount; var currentTypeArguments = typeArguments.Slice(typeArgumentsOffset, genericArgumentsCount).ToArray(); - var formattedTypes = currentTypeArguments.Select(t => FormatDisplayString(t, 0, t.GetGenericArguments())); + var formattedTypes + = currentTypeArguments.Select(static t => FormatDisplayString(t, 0, t.GetGenericArguments())); // Standard generic types are displayed as Foo displayName = $"{tokens[0]}<{string.Join(", ", formattedTypes)}>"; @@ -130,7 +131,7 @@ public static class TypeExtensions } // Atomically get or build the display string for the current type. - return DisplayNames.GetValue(type, t => + return DisplayNames.GetValue(type, static t => { // By-ref types are displayed as T& if (t.IsByRef) @@ -159,4 +160,4 @@ public static class TypeExtensions return FormatDisplayString(t, 0, t.GetGenericArguments()); }); } -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Extensions/ValueTypeExtensions.cs b/Source/FisheryLib/Diagnostics/Extensions/ValueTypeExtensions.cs index 54511d9..0201060 100644 --- a/Source/FisheryLib/Diagnostics/Extensions/ValueTypeExtensions.cs +++ b/Source/FisheryLib/Diagnostics/Extensions/ValueTypeExtensions.cs @@ -14,13 +14,8 @@ public static class ValueTypeExtensions /// /// Gets the table of hex characters (doesn't allocate, maps to .text section, see ). /// - private static ReadOnlySpan HexCharactersTable => new[] - { - (byte)'0', (byte)'1', (byte)'2', (byte)'3', - (byte)'4', (byte)'5', (byte)'6', (byte)'7', - (byte)'8', (byte)'9', (byte)'A', (byte)'B', - (byte)'C', (byte)'D', (byte)'E', (byte)'F' - }; + private static ReadOnlySpan HexCharactersTable + => "0123456789ABCDEF"u8; /// /// Returns a hexadecimal representation of a given value, left-padded and ordered as big-endian. @@ -42,7 +37,7 @@ public static class ValueTypeExtensions public static unsafe string ToHexString(this T value) where T : unmanaged { - var sizeOfT = Unsafe.SizeOf(); + var sizeOfT = sizeof(T); var bufferSize = (2 * sizeOfT) + 2; var p = stackalloc char[bufferSize]; @@ -63,4 +58,4 @@ public static class ValueTypeExtensions return new(p, 0, bufferSize); } -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Generated/Guard.Collection.tt b/Source/FisheryLib/Diagnostics/Generated/Guard.Collection.tt index 4784e18..6b230cb 100644 --- a/Source/FisheryLib/Diagnostics/Generated/Guard.Collection.tt +++ b/Source/FisheryLib/Diagnostics/Generated/Guard.Collection.tt @@ -10,287 +10,287 @@ namespace PerformanceFish.Utility.Diagnostics; partial class Guard { <# -GenerateTextForItems(EnumerableTypes, item => -{ + GenerateTextForItems(EnumerableTypes, item => + { #> /// - /// Asserts that the input <#=item.XmlType#> instance must be empty. + /// Asserts that the input <#= item.XmlType #> instance must be empty. /// - /// The item of items in the input <#=item.XmlType#> instance. - /// The input <#=item.XmlType#> instance to check the size for. + /// The item of items in the input <#= item.XmlType #> instance. + /// The input <#= item.XmlType #> instance to check the size for. /// The name of the input parameter being tested. - /// Thrown if the size of is != 0. + /// Thrown if the size of is != 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEmpty(<#=item.Type#> <#=item.Name#>, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void IsEmpty(<#= item.Type #> <#= item.Name #>, [CallerArgumentExpression("<#= item.Name #>")] string name = "") { - if (<#=item.Name#>.<#=item.Size#> == 0) + if (<#= item.Name #>.<#= item.Size #> == 0) { return; } - ThrowHelper.ThrowArgumentExceptionForIsEmpty(<#=item.Cast#><#=item.Name#>, name); + ThrowHelper.ThrowArgumentExceptionForIsEmpty(<#= item.Cast #><#= item.Name #>, name); } /// - /// Asserts that the input <#=item.XmlType#> instance must not be empty. + /// Asserts that the input <#= item.XmlType #> instance must not be empty. /// - /// The item of items in the input <#=item.XmlType#> instance. - /// The input <#=item.XmlType#> instance to check the size for. + /// The item of items in the input <#= item.XmlType #> instance. + /// The input <#= item.XmlType #> instance to check the size for. /// The name of the input parameter being tested. - /// Thrown if the size of is == 0. + /// Thrown if the size of is == 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEmpty(<#=item.Type#> <#=item.Name#>, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void IsNotEmpty(<#= item.Type #> <#= item.Name #>, [CallerArgumentExpression("<#= item.Name #>")] string name = "") { - if (<#=item.Name#>.<#=item.Size#> != 0) + if (<#= item.Name #>.<#= item.Size #> != 0) { return; } <# -if (item.Type == "Span") -{ + if (item.Type == "Span") + { #> ThrowHelper.ThrowArgumentExceptionForIsNotEmptyWithSpan(name); <# -} -else if (item.Type == "ReadOnlySpan") -{ + } + else if (item.Type == "ReadOnlySpan") + { #> ThrowHelper.ThrowArgumentExceptionForIsNotEmptyWithReadOnlySpan(name); <# -} -else -{ + } + else + { #> - ThrowHelper.ThrowArgumentExceptionForIsNotEmpty<<#=item.Type#>>(name); + ThrowHelper.ThrowArgumentExceptionForIsNotEmpty<<#= item.Type #>>(name); <# -} + } #> } /// - /// Asserts that the input <#=item.XmlType#> instance must have a size of a specified value. + /// Asserts that the input <#= item.XmlType #> instance must have a size of a specified value. /// - /// The item of items in the input <#=item.XmlType#> instance. - /// The input <#=item.XmlType#> instance to check the size for. + /// The item of items in the input <#= item.XmlType #> instance. + /// The input <#= item.XmlType #> instance to check the size for. /// The target size to test. /// The name of the input parameter being tested. - /// Thrown if the size of is != . + /// Thrown if the size of is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void HasSizeEqualTo(<#= item.Type #> <#= item.Name #>, int size, [CallerArgumentExpression("<#= item.Name #>")] string name = "") { - if (<#=item.Name#>.<#=item.Size#> == size) + if (<#= item.Name #>.<#= item.Size #> == size) { return; } - ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(<#=item.Cast#><#=item.Name#>, size, name); + ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(<#= item.Cast #><#= item.Name #>, size, name); } /// - /// Asserts that the input <#=item.XmlType#> instance must have a size not equal to a specified value. + /// Asserts that the input <#= item.XmlType #> instance must have a size not equal to a specified value. /// - /// The item of items in the input <#=item.XmlType#> instance. - /// The input <#=item.XmlType#> instance to check the size for. + /// The item of items in the input <#= item.XmlType #> instance. + /// The input <#= item.XmlType #> instance to check the size for. /// The target size to test. /// The name of the input parameter being tested. - /// Thrown if the size of is == . + /// Thrown if the size of is == . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeNotEqualTo(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void HasSizeNotEqualTo(<#= item.Type #> <#= item.Name #>, int size, [CallerArgumentExpression("<#= item.Name #>")] string name = "") { - if (<#=item.Name#>.<#=item.Size#> != size) + if (<#= item.Name #>.<#= item.Size #> != size) { return; } - ThrowHelper.ThrowArgumentExceptionForHasSizeNotEqualTo(<#=item.Cast#><#=item.Name#>, size, name); + ThrowHelper.ThrowArgumentExceptionForHasSizeNotEqualTo(<#= item.Cast #><#= item.Name #>, size, name); } /// - /// Asserts that the input <#=item.XmlType#> instance must have a size over a specified value. + /// Asserts that the input <#= item.XmlType #> instance must have a size over a specified value. /// - /// The item of items in the input <#=item.XmlType#> instance. - /// The input <#=item.XmlType#> instance to check the size for. + /// The item of items in the input <#= item.XmlType #> instance. + /// The input <#= item.XmlType #> instance to check the size for. /// The target size to test. /// The name of the input parameter being tested. - /// Thrown if the size of is <= . + /// Thrown if the size of is <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThan(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void HasSizeGreaterThan(<#= item.Type #> <#= item.Name #>, int size, [CallerArgumentExpression("<#= item.Name #>")] string name = "") { - if (<#=item.Name#>.<#=item.Size#> > size) + if (<#= item.Name #>.<#= item.Size #> > size) { return; } - ThrowHelper.ThrowArgumentExceptionForHasSizeGreaterThan(<#=item.Cast#><#=item.Name#>, size, name); + ThrowHelper.ThrowArgumentExceptionForHasSizeGreaterThan(<#= item.Cast #><#= item.Name #>, size, name); } /// - /// Asserts that the input <#=item.XmlType#> instance must have a size of at least or equal to a specified value. + /// Asserts that the input <#= item.XmlType #> instance must have a size of at least or equal to a specified value. /// - /// The item of items in the input <#=item.XmlType#> instance. - /// The input <#=item.XmlType#> instance to check the size for. + /// The item of items in the input <#= item.XmlType #> instance. + /// The input <#= item.XmlType #> instance to check the size for. /// The target size to test. /// The name of the input parameter being tested. - /// Thrown if the size of is < . + /// Thrown if the size of is < . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThanOrEqualTo(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void HasSizeGreaterThanOrEqualTo(<#= item.Type #> <#= item.Name #>, int size, [CallerArgumentExpression("<#= item.Name #>")] string name = "") { - if (<#=item.Name#>.<#=item.Size#> >= size) + if (<#= item.Name #>.<#= item.Size #> >= size) { return; } - ThrowHelper.ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo(<#=item.Cast#><#=item.Name#>, size, name); + ThrowHelper.ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo(<#= item.Cast #><#= item.Name #>, size, name); } /// - /// Asserts that the input <#=item.XmlType#> instance must have a size of less than a specified value. + /// Asserts that the input <#= item.XmlType #> instance must have a size of less than a specified value. /// - /// The item of items in the input <#=item.XmlType#> instance. - /// The input <#=item.XmlType#> instance to check the size for. + /// The item of items in the input <#= item.XmlType #> instance. + /// The input <#= item.XmlType #> instance to check the size for. /// The target size to test. /// The name of the input parameter being tested. - /// Thrown if the size of is >= . + /// Thrown if the size of is >= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThan(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void HasSizeLessThan(<#= item.Type #> <#= item.Name #>, int size, [CallerArgumentExpression("<#= item.Name #>")] string name = "") { - if (<#=item.Name#>.<#=item.Size#> < size) + if (<#= item.Name #>.<#= item.Size #> < size) { return; } - ThrowHelper.ThrowArgumentExceptionForHasSizeLessThan(<#=item.Cast#><#=item.Name#>, size, name); + ThrowHelper.ThrowArgumentExceptionForHasSizeLessThan(<#= item.Cast #><#= item.Name #>, size, name); } /// - /// Asserts that the input <#=item.XmlType#> instance must have a size of less than or equal to a specified value. + /// Asserts that the input <#= item.XmlType #> instance must have a size of less than or equal to a specified value. /// - /// The item of items in the input <#=item.XmlType#> instance. - /// The input <#=item.XmlType#> instance to check the size for. + /// The item of items in the input <#= item.XmlType #> instance. + /// The input <#= item.XmlType #> instance to check the size for. /// The target size to test. /// The name of the input parameter being tested. - /// Thrown if the size of is > . + /// Thrown if the size of is > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(<#=item.Type#> <#=item.Name#>, int size, [CallerArgumentExpression("<#=item.Name#>")] string name = "") + public static void HasSizeLessThanOrEqualTo(<#= item.Type #> <#= item.Name #>, int size, [CallerArgumentExpression("<#= item.Name #>")] string name = "") { - if (<#=item.Name#>.<#=item.Size#> <= size) + if (<#= item.Name #>.<#= item.Size #> <= size) { return; } - ThrowHelper.ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(<#=item.Cast#><#=item.Name#>, size, name); + ThrowHelper.ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(<#= item.Cast #><#= item.Name #>, size, name); } /// - /// Asserts that the source <#=item.XmlType#> instance must have the same size of a destination <#=item.XmlType#> instance. + /// Asserts that the source <#= item.XmlType #> instance must have the same size of a destination <#= item.XmlType #> instance. /// - /// The item of items in the input <#=item.XmlType#> instance. - /// The source <#=item.XmlType#> instance to check the size for. - /// The destination <#=item.XmlType#> instance to check the size for. + /// The item of items in the input <#= item.XmlType #> instance. + /// The source <#= item.XmlType #> instance to check the size for. + /// The destination <#= item.XmlType #> instance to check the size for. /// The name of the input parameter being tested. /// Thrown if the size of is != the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(<#=item.Type#> source, <#=item.DestinationType#> destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeEqualTo(<#= item.Type #> source, <#= item.DestinationType #> destination, [CallerArgumentExpression("source")] string name = "") { - if (source.<#=item.Size#> == destination.<#=item.Size#>) + if (source.<#= item.Size #> == destination.<#= item.Size #>) { return; } <# -if (item.HasCountProperty) -{ + if (item.HasCountProperty) + { #> - ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(<#=item.Cast#>source, destination.<#=item.Size#>, name); + ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(<#= item.Cast #>source, destination.<#= item.Size #>, name); <# -} -else -{ + } + else + { #> - ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(source, <#=item.Cast#>destination, name); + ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(source, <#= item.Cast #>destination, name); <# -} + } #> } /// - /// Asserts that the source <#=item.XmlType#> instance must have a size of less than or equal to that of a destination <#=item.XmlType#> instance. + /// Asserts that the source <#= item.XmlType #> instance must have a size of less than or equal to that of a destination <#= item.XmlType #> instance. /// - /// The item of items in the input <#=item.XmlType#> instance. - /// The source <#=item.XmlType#> instance to check the size for. - /// The destination <#=item.XmlType#> instance to check the size for. + /// The item of items in the input <#= item.XmlType #> instance. + /// The source <#= item.XmlType #> instance to check the size for. + /// The destination <#= item.XmlType #> instance to check the size for. /// The name of the input parameter being tested. /// Thrown if the size of is > the one of . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(<#=item.Type#> source, <#=item.DestinationType#> destination, [CallerArgumentExpression("source")] string name = "") + public static void HasSizeLessThanOrEqualTo(<#= item.Type #> source, <#= item.DestinationType #> destination, [CallerArgumentExpression("source")] string name = "") { - if (source.<#=item.Size#> <= destination.<#=item.Size#>) + if (source.<#= item.Size #> <= destination.<#= item.Size #>) { return; } <# -if (item.HasCountProperty) -{ + if (item.HasCountProperty) + { #> - ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(<#=item.Cast#>source, destination.<#=item.Size#>, name); + ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(<#= item.Cast #>source, destination.<#= item.Size #>, name); <# -} -else -{ + } + else + { #> - ThrowHelper.ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(source, <#=item.Cast#>destination, name); + ThrowHelper.ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(source, <#= item.Cast #>destination, name); <# -} + } #> } /// - /// Asserts that the input index is valid for a given <#=item.XmlType#> instance. + /// Asserts that the input index is valid for a given <#= item.XmlType #> instance. /// - /// The item of items in the input <#=item.XmlType#> instance. - /// The input index to be used to access . - /// The input <#=item.XmlType#> instance to use to validate . + /// The item of items in the input <#= item.XmlType #> instance. + /// The input index to be used to access . + /// The input <#= item.XmlType #> instance to use to validate . /// The name of the input parameter being tested. - /// Thrown if is not valid to access . + /// Thrown if is not valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRangeFor(int index, <#=item.Type#> <#=item.Name#>, [CallerArgumentExpression("index")] string name = "") + public static void IsInRangeFor(int index, <#= item.Type #> <#= item.Name #>, [CallerArgumentExpression("index")] string name = "") { <# -// Here we're leveraging the fact that signed integers are represented -// in 2-complement to perform the bounds check with a single compare operation. -// This is the same trick used throughout CoreCLR as well. -// For more info and code sample, see the original conversation here: -// https://github.com/CommunityToolkit/WindowsCommunityToolkit/pull/3131#discussion_r390682835 + // Here we're leveraging the fact that signed integers are represented + // in 2-complement to perform the bounds check with a single compare operation. + // This is the same trick used throughout CoreCLR as well. + // For more info and code sample, see the original conversation here: + // https://github.com/CommunityToolkit/WindowsCommunityToolkit/pull/3131#discussion_r390682835 #> - if ((uint)index < (uint)<#=item.Name#>.<#=item.Size#>) + if ((uint)index < (uint)<#= item.Name #>.<#= item.Size #>) { return; } - ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsInRangeFor(index, <#=item.Cast#><#=item.Name#>, name); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsInRangeFor(index, <#= item.Cast #><#= item.Name #>, name); } /// - /// Asserts that the input index is not valid for a given <#=item.XmlType#> instance. + /// Asserts that the input index is not valid for a given <#= item.XmlType #> instance. /// - /// The item of items in the input <#=item.XmlType#> instance. - /// The input index to be used to access . - /// The input <#=item.XmlType#> instance to use to validate . + /// The item of items in the input <#= item.XmlType #> instance. + /// The input index to be used to access . + /// The input <#= item.XmlType #> instance to use to validate . /// The name of the input parameter being tested. - /// Thrown if is valid to access . + /// Thrown if is valid to access . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRangeFor(int index, <#=item.Type#> <#=item.Name#>, [CallerArgumentExpression("index")] string name = "") + public static void IsNotInRangeFor(int index, <#= item.Type #> <#= item.Name #>, [CallerArgumentExpression("index")] string name = "") { - if ((uint)index >= (uint)<#=item.Name#>.<#=item.Size#>) + if ((uint)index >= (uint)<#= item.Name #>.<#= item.Size #>) { return; } - ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor(index, <#=item.Cast#><#=item.Name#>, name); + ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor(index, <#= item.Cast #><#= item.Name #>, name); } <# -}); + }); #> } diff --git a/Source/FisheryLib/Diagnostics/Generated/Guard.Comparable.Numeric.tt b/Source/FisheryLib/Diagnostics/Generated/Guard.Comparable.Numeric.tt index 23b4af2..2c0c417 100644 --- a/Source/FisheryLib/Diagnostics/Generated/Guard.Comparable.Numeric.tt +++ b/Source/FisheryLib/Diagnostics/Generated/Guard.Comparable.Numeric.tt @@ -10,19 +10,19 @@ namespace PerformanceFish.Utility.Diagnostics; partial class Guard { <# -GenerateTextForItems(NumericTypes, typeInfo => -{ -var (type, prefix) = typeInfo; + GenerateTextForItems(NumericTypes, typeInfo => + { + var (type, prefix) = typeInfo; #> /// /// Asserts that the input value must be equal to a specified value. /// - /// The input ="<#=type#>"/> value to test. - /// The target ="<#=type#>"/> value to test for. + /// The input ="<#= type #>"/> value to test. + /// The target ="<#= type #>"/> value to test for. /// The name of the input parameter being tested. /// Thrown if is != . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsEqualTo(<#=type#> value, <#=type#> target, [CallerArgumentExpression("value")] string name = "") + public static void IsEqualTo(<#= type #> value, <#= type #> target, [CallerArgumentExpression("value")] string name = "") { if (value == target) { @@ -35,13 +35,13 @@ var (type, prefix) = typeInfo; /// /// Asserts that the input value must be not equal to a specified value. /// - /// The input ="<#=type#>"/> value to test. - /// The target ="<#=type#>"/> value to test for. + /// The input ="<#= type #>"/> value to test. + /// The target ="<#= type #>"/> value to test for. /// The name of the input parameter being tested. /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotEqualTo(<#=type#> value, <#=type#> target, [CallerArgumentExpression("value")] string name = "") + public static void IsNotEqualTo(<#= type #> value, <#= type #> target, [CallerArgumentExpression("value")] string name = "") { if (value != target) { @@ -54,13 +54,13 @@ var (type, prefix) = typeInfo; /// /// Asserts that the input value must be less than a specified value. /// - /// The input ="<#=type#>"/> value to test. - /// The exclusive maximum ="<#=type#>"/> value that is accepted. + /// The input ="<#= type #>"/> value to test. + /// The exclusive maximum ="<#= type #>"/> value that is accepted. /// The name of the input parameter being tested. /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThan(<#=type#> value, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThan(<#= type #> value, <#= type #> maximum, [CallerArgumentExpression("value")] string name = "") { if (value < maximum) { @@ -73,13 +73,13 @@ var (type, prefix) = typeInfo; /// /// Asserts that the input value must be less than or equal to a specified value. /// - /// The input ="<#=type#>"/> value to test. - /// The inclusive maximum ="<#=type#>"/> value that is accepted. + /// The input ="<#= type #>"/> value to test. + /// The inclusive maximum ="<#= type #>"/> value that is accepted. /// The name of the input parameter being tested. /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsLessThanOrEqualTo(<#=type#> value, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsLessThanOrEqualTo(<#= type #> value, <#= type #> maximum, [CallerArgumentExpression("value")] string name = "") { if (value <= maximum) { @@ -92,13 +92,13 @@ var (type, prefix) = typeInfo; /// /// Asserts that the input value must be greater than a specified value. /// - /// The input ="<#=type#>"/> value to test. - /// The exclusive minimum ="<#=type#>"/> value that is accepted. + /// The input ="<#= type #>"/> value to test. + /// The exclusive minimum ="<#= type #>"/> value that is accepted. /// The name of the input parameter being tested. /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThan(<#=type#> value, <#=type#> minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThan(<#= type #> value, <#= type #> minimum, [CallerArgumentExpression("value")] string name = "") { if (value > minimum) { @@ -111,13 +111,13 @@ var (type, prefix) = typeInfo; /// /// Asserts that the input value must be greater than or equal to a specified value. /// - /// The input ="<#=type#>"/> value to test. - /// The inclusive minimum ="<#=type#>"/> value that is accepted. + /// The input ="<#= type #>"/> value to test. + /// The inclusive minimum ="<#= type #>"/> value that is accepted. /// The name of the input parameter being tested. /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(<#=type#> value, <#=type#> minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(<#= type #> value, <#= type #> minimum, [CallerArgumentExpression("value")] string name = "") { if (value >= minimum) { @@ -130,9 +130,9 @@ var (type, prefix) = typeInfo; /// /// Asserts that the input value must be in a given range. /// - /// The input ="<#=type#>"/> value to test. - /// The inclusive minimum ="<#=type#>"/> value that is accepted. - /// The exclusive maximum ="<#=type#>"/> value that is accepted. + /// The input ="<#= type #>"/> value to test. + /// The inclusive minimum ="<#= type #>"/> value that is accepted. + /// The exclusive maximum ="<#= type #>"/> value that is accepted. /// The name of the input parameter being tested. /// Thrown if is < or >= . /// @@ -140,7 +140,7 @@ var (type, prefix) = typeInfo; /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsInRange(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsInRange(<#= type #> value, <#= type #> minimum, <#= type #> maximum, [CallerArgumentExpression("value")] string name = "") { if (value >= minimum && value < maximum) { @@ -153,9 +153,9 @@ var (type, prefix) = typeInfo; /// /// Asserts that the input value must not be in a given range. /// - /// The input ="<#=type#>"/> value to test. - /// The inclusive minimum ="<#=type#>"/> value that is accepted. - /// The exclusive maximum ="<#=type#>"/> value that is accepted. + /// The input ="<#= type #>"/> value to test. + /// The inclusive minimum ="<#= type #>"/> value that is accepted. + /// The exclusive maximum ="<#= type #>"/> value that is accepted. /// The name of the input parameter being tested. /// Thrown if is >= or < . /// @@ -163,7 +163,7 @@ var (type, prefix) = typeInfo; /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(<#= type #> value, <#= type #> minimum, <#= type #> maximum, [CallerArgumentExpression("value")] string name = "") { if (value < minimum || value >= maximum) { @@ -176,9 +176,9 @@ var (type, prefix) = typeInfo; /// /// Asserts that the input value must be in a given interval. /// - /// The input ="<#=type#>"/> value to test. - /// The exclusive minimum ="<#=type#>"/> value that is accepted. - /// The exclusive maximum ="<#=type#>"/> value that is accepted. + /// The input ="<#= type #>"/> value to test. + /// The exclusive minimum ="<#= type #>"/> value that is accepted. + /// The exclusive maximum ="<#= type #>"/> value that is accepted. /// The name of the input parameter being tested. /// Thrown if is <= or >= . /// @@ -186,7 +186,7 @@ var (type, prefix) = typeInfo; /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetween(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetween(<#= type #> value, <#= type #> minimum, <#= type #> maximum, [CallerArgumentExpression("value")] string name = "") { if (value > minimum && value < maximum) { @@ -199,9 +199,9 @@ var (type, prefix) = typeInfo; /// /// Asserts that the input value must not be in a given interval. /// - /// The input ="<#=type#>"/> value to test. - /// The exclusive minimum ="<#=type#>"/> value that is accepted. - /// The exclusive maximum ="<#=type#>"/> value that is accepted. + /// The input ="<#= type #>"/> value to test. + /// The exclusive minimum ="<#= type #>"/> value that is accepted. + /// The exclusive maximum ="<#= type #>"/> value that is accepted. /// The name of the input parameter being tested. /// Thrown if is > or < . /// @@ -209,7 +209,7 @@ var (type, prefix) = typeInfo; /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(<#= type #> value, <#= type #> minimum, <#= type #> maximum, [CallerArgumentExpression("value")] string name = "") { if (value <= minimum || value >= maximum) { @@ -222,9 +222,9 @@ var (type, prefix) = typeInfo; /// /// Asserts that the input value must be in a given interval. /// - /// The input ="<#=type#>"/> value to test. - /// The inclusive minimum ="<#=type#>"/> value that is accepted. - /// The inclusive maximum ="<#=type#>"/> value that is accepted. + /// The input ="<#= type #>"/> value to test. + /// The inclusive minimum ="<#= type #>"/> value that is accepted. + /// The inclusive maximum ="<#= type #>"/> value that is accepted. /// The name of the input parameter being tested. /// Thrown if is < or > . /// @@ -232,7 +232,7 @@ var (type, prefix) = typeInfo; /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(<#= type #> value, <#= type #> minimum, <#= type #> maximum, [CallerArgumentExpression("value")] string name = "") { if (value >= minimum && value <= maximum) { @@ -245,9 +245,9 @@ var (type, prefix) = typeInfo; /// /// Asserts that the input value must not be in a given interval. /// - /// The input ="<#=type#>"/> value to test. - /// The inclusive minimum ="<#=type#>"/> value that is accepted. - /// The inclusive maximum ="<#=type#>"/> value that is accepted. + /// The input ="<#= type #>"/> value to test. + /// The inclusive minimum ="<#= type #>"/> value that is accepted. + /// The inclusive maximum ="<#= type #>"/> value that is accepted. /// The name of the input parameter being tested. /// Thrown if is >= or <= . /// @@ -255,7 +255,7 @@ var (type, prefix) = typeInfo; /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(<#=type#> value, <#=type#> minimum, <#=type#> maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(<#= type #> value, <#= type #> minimum, <#= type #> maximum, [CallerArgumentExpression("value")] string name = "") { if (value < minimum || value > maximum) { @@ -265,6 +265,6 @@ var (type, prefix) = typeInfo; ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotBetweenOrEqualTo(value, minimum, maximum, name); } <# -}); + }); #> } diff --git a/Source/FisheryLib/Diagnostics/Generated/Guard.md b/Source/FisheryLib/Diagnostics/Generated/Guard.md index b787eda..3af4878 100644 --- a/Source/FisheryLib/Diagnostics/Generated/Guard.md +++ b/Source/FisheryLib/Diagnostics/Generated/Guard.md @@ -1,17 +1,29 @@ # T4 templates and generated APIs -This folder contains a number of template files (with the `.tt` or `.ttinclude` extensions) and the generated `.g.cs` files generated by those templates. The template files use the T4 format, which is natively supported by Visual Studio (more info is available [here](https://docs.microsoft.com/visualstudio/modeling/code-generation-and-t4-text-templates)). +This folder contains a number of template files (with the `.tt` or `.ttinclude` extensions) and the generated `.g.cs` +files generated by those templates. The template files use the T4 format, which is natively supported by Visual Studio ( +more info is available [here](https://docs.microsoft.com/visualstudio/modeling/code-generation-and-t4-text-templates)). ## Why is this needed? There are a few reasons why T4 templates are used for the `Guard` class: -- Especially when many overloads are available for an API, using templates makes it much easier to maintain code and spot mistakes, as the actual number of lines of code to review is much smaller: it's just the same code from each template! -- Using type-specific overloads instead of generic methods can result in faster code. For instance, T4 templates are used to generate overloads for comparison APIs (eg. `Guard.IsGreaterThan(int, int, string)`). This results in more compact and optimized code as opposed to a generic method using `where T : IComparable` as type constraint. -- In some cases, using generic methods just isn't possible. For instance, types like `Span` and `ReadOnlySpan` can't be used as generic type parameters, and even if that had been possible, they don't implement an interface we could use in the generic type constraint. Using T4 templates solves this issue, as we can just have specialized method for each supported type or collection type. +- Especially when many overloads are available for an API, using templates makes it much easier to maintain code and + spot mistakes, as the actual number of lines of code to review is much smaller: it's just the same code from each + template! +- Using type-specific overloads instead of generic methods can result in faster code. For instance, T4 templates are + used to generate overloads for comparison APIs (eg. `Guard.IsGreaterThan(int, int, string)`). This results in more + compact and optimized code as opposed to a generic method using `where T : IComparable` as type constraint. +- In some cases, using generic methods just isn't possible. For instance, types like `Span` and `ReadOnlySpan` + can't be used as generic type parameters, and even if that had been possible, they don't implement an interface we + could use in the generic type constraint. Using T4 templates solves this issue, as we can just have specialized method + for each supported type or collection type. ## How to make changes -If you need to change an API that is declared in a template, or to add a new one, just edit the right `.tt` file and save it: Visual Studio will take care of updating the generated `.g.cs` file automatically. Don't make changes to those generated `.g.cs` files directly, as those will be overwritten as soon as their source template is updated. +If you need to change an API that is declared in a template, or to add a new one, just edit the right `.tt` file and +save it: Visual Studio will take care of updating the generated `.g.cs` file automatically. Don't make changes to those +generated `.g.cs` files directly, as those will be overwritten as soon as their source template is updated. -Note that all the `.g.cs` files are checked in into the repository, so if you do make changes to a template file, make sure to also include the updated `.g.cs` file in your commits. +Note that all the `.g.cs` files are checked in into the repository, so if you do make changes to a template file, make +sure to also include the updated `.g.cs` file in your commits. diff --git a/Source/FisheryLib/Diagnostics/Generated/ThrowHelper.Collection.tt b/Source/FisheryLib/Diagnostics/Generated/ThrowHelper.Collection.tt index 86e7aa1..ed7d5bb 100644 --- a/Source/FisheryLib/Diagnostics/Generated/ThrowHelper.Collection.tt +++ b/Source/FisheryLib/Diagnostics/Generated/ThrowHelper.Collection.tt @@ -13,109 +13,109 @@ partial class Guard partial class ThrowHelper { <# -GenerateTextForItems(EnumerableTypes, item => -{ + GenerateTextForItems(EnumerableTypes, item => + { #> /// /// Throws an when (or an overload) fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsEmpty(<#=item.Type#> <#=item.Name#>, string name) + public static void ThrowArgumentExceptionForIsEmpty(<#= item.Type #> <#= item.Name #>, string name) { - throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must be empty, had a size of {AssertString(<#=item.Name#>.<#=item.Size#>)}.", name); + throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#= item.Type #>).ToTypeString()}) must be empty, had a size of {AssertString(<#= item.Name #>.<#= item.Size #>)}.", name); } /// /// Throws an when (or an overload) fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForHasSizeEqualTo(<#=item.Type#> <#=item.Name#>, int size, string name) + public static void ThrowArgumentExceptionForHasSizeEqualTo(<#= item.Type #> <#= item.Name #>, int size, string name) { - throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size equal to {size}, had a size of {AssertString(<#=item.Name#>.<#=item.Size#>)}.", name); + throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#= item.Type #>).ToTypeString()}) must have a size equal to {size}, had a size of {AssertString(<#= item.Name #>.<#= item.Size #>)}.", name); } /// /// Throws an when (or an overload) fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForHasSizeNotEqualTo(<#=item.Type#> <#=item.Name#>, int size, string name) + public static void ThrowArgumentExceptionForHasSizeNotEqualTo(<#= item.Type #> <#= item.Name #>, int size, string name) { - throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size not equal to {size}, had a size of {AssertString(<#=item.Name#>.<#=item.Size#>)}.", name); + throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#= item.Type #>).ToTypeString()}) must have a size not equal to {size}, had a size of {AssertString(<#= item.Name #>.<#= item.Size #>)}.", name); } /// /// Throws an when (or an overload) fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForHasSizeGreaterThan(<#=item.Type#> <#=item.Name#>, int size, string name) + public static void ThrowArgumentExceptionForHasSizeGreaterThan(<#= item.Type #> <#= item.Name #>, int size, string name) { - throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size over {size}, had a size of {AssertString(<#=item.Name#>.<#=item.Size#>)}.", name); + throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#= item.Type #>).ToTypeString()}) must have a size over {size}, had a size of {AssertString(<#= item.Name #>.<#= item.Size #>)}.", name); } /// /// Throws an when (or an overload) fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo(<#=item.Type#> <#=item.Name#>, int size, string name) + public static void ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo(<#= item.Type #> <#= item.Name #>, int size, string name) { - throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size of at least {size}, had a size of {AssertString(<#=item.Name#>.<#=item.Size#>)}.", name); + throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#= item.Type #>).ToTypeString()}) must have a size of at least {size}, had a size of {AssertString(<#= item.Name #>.<#= item.Size #>)}.", name); } /// /// Throws an when (or an overload) fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForHasSizeLessThan(<#=item.Type#> <#=item.Name#>, int size, string name) + public static void ThrowArgumentExceptionForHasSizeLessThan(<#= item.Type #> <#= item.Name #>, int size, string name) { - throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size less than {size}, had a size of {AssertString(<#=item.Name#>.<#=item.Size#>)}.", name); + throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#= item.Type #>).ToTypeString()}) must have a size less than {size}, had a size of {AssertString(<#= item.Name #>.<#= item.Size #>)}.", name); } /// /// Throws an when (or an overload) fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(<#=item.Type#> <#=item.Name#>, int size, string name) + public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(<#= item.Type #> <#= item.Name #>, int size, string name) { - throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size less than or equal to {size}, had a size of {AssertString(<#=item.Name#>.<#=item.Size#>)}.", name); + throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(<#= item.Type #>).ToTypeString()}) must have a size less than or equal to {size}, had a size of {AssertString(<#= item.Name #>.<#= item.Size #>)}.", name); } /// /// Throws an when (or an overload) fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForHasSizeEqualTo(<#=item.Type#> source, <#=item.DestinationType#> destination, string name) + public static void ThrowArgumentExceptionForHasSizeEqualTo(<#= item.Type #> source, <#= item.DestinationType #> destination, string name) { - throw new ArgumentException($"The source {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size equal to {AssertString(destination.<#=item.Size#>)} (the destination), had a size of {AssertString(source.<#=item.Size#>)}.", name); + throw new ArgumentException($"The source {AssertString(name)} ({typeof(<#= item.Type #>).ToTypeString()}) must have a size equal to {AssertString(destination.<#= item.Size #>)} (the destination), had a size of {AssertString(source.<#= item.Size #>)}.", name); } /// /// Throws an when (or an overload) fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(<#=item.Type#> source, <#=item.DestinationType#> destination, string name) + public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(<#= item.Type #> source, <#= item.DestinationType #> destination, string name) { - throw new ArgumentException($"The source {AssertString(name)} ({typeof(<#=item.Type#>).ToTypeString()}) must have a size less than or equal to {AssertString(destination.<#=item.Size#>)} (the destination), had a size of {AssertString(source.<#=item.Size#>)}.", name); + throw new ArgumentException($"The source {AssertString(name)} ({typeof(<#= item.Type #>).ToTypeString()}) must have a size less than or equal to {AssertString(destination.<#= item.Size #>)} (the destination), had a size of {AssertString(source.<#= item.Size #>)}.", name); } /// /// Throws an when (or an overload) fails. /// [DoesNotReturn] - public static void ThrowArgumentOutOfRangeExceptionForIsInRangeFor(int index, <#=item.Type#> <#=item.Name#>, string name) + public static void ThrowArgumentOutOfRangeExceptionForIsInRangeFor(int index, <#= item.Type #> <#= item.Name #>, string name) { - throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must be in the range given by <0> and {AssertString(<#=item.Name#>.<#=item.Size#>)} to be a valid index for the target collection ({typeof(<#=item.Type#>).ToTypeString()}), was {AssertString(index)}."); + throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must be in the range given by <0> and {AssertString(<#= item.Name #>.<#= item.Size #>)} to be a valid index for the target collection ({typeof(<#= item.Type #>).ToTypeString()}), was {AssertString(index)}."); } /// /// Throws an when (or an overload) fails. /// [DoesNotReturn] - public static void ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor(int index, <#=item.Type#> <#=item.Name#>, string name) + public static void ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor(int index, <#= item.Type #> <#= item.Name #>, string name) { - throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must not be in the range given by <0> and {AssertString(<#=item.Name#>.<#=item.Size#>)} to be an invalid index for the target collection ({typeof(<#=item.Type#>).ToTypeString()}), was {AssertString(index)}."); + throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must not be in the range given by <0> and {AssertString(<#= item.Name #>.<#= item.Size #>)} to be an invalid index for the target collection ({typeof(<#= item.Type #>).ToTypeString()}), was {AssertString(index)}."); } <# -}); + }); #> } } diff --git a/Source/FisheryLib/Diagnostics/Generated/TypeInfo.ttinclude b/Source/FisheryLib/Diagnostics/Generated/TypeInfo.ttinclude index b2421dc..b320d9e 100644 --- a/Source/FisheryLib/Diagnostics/Generated/TypeInfo.ttinclude +++ b/Source/FisheryLib/Diagnostics/Generated/TypeInfo.ttinclude @@ -71,11 +71,14 @@ new EnumerableTypeInfo("Span", "", "span", "Length", "Span", ""), new EnumerableTypeInfo("ReadOnlySpan", "", "span", "Length", "Span", ""), new EnumerableTypeInfo("Memory", "", "memory", "Length", "Memory", ""), - new EnumerableTypeInfo("ReadOnlyMemory", "", "memory", "Length", "Memory", ""), + new EnumerableTypeInfo("ReadOnlyMemory", "", "memory", "Length", "Memory", + ""), new EnumerableTypeInfo("T[]", " array", "array", "Length", "T[]", ""), new EnumerableTypeInfo("List", "", "list", "Count", "List", "(ICollection)"), - new EnumerableTypeInfo("ICollection", "", "collection", "Count", "ICollection", ""), - new EnumerableTypeInfo("IReadOnlyCollection", "", "collection", "Count", "ICollection", ""), + new EnumerableTypeInfo("ICollection", "", "collection", "Count", + "ICollection", ""), + new EnumerableTypeInfo("IReadOnlyCollection", "", "collection", "Count", + "ICollection", "") }; /// @@ -107,10 +110,11 @@ for (int i = 0; i < items.Count; i++) { // Insert a blank line after the first item - if (i > 0) WriteLine(""); + if (i > 0) + WriteLine(""); // Invoke the factory with the current item - factory(items [i]); + factory(items[i]); } } #> \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Guard.Boolean.cs b/Source/FisheryLib/Diagnostics/Guard.Boolean.cs index 7d38f1e..46327c4 100644 --- a/Source/FisheryLib/Diagnostics/Guard.Boolean.cs +++ b/Source/FisheryLib/Diagnostics/Guard.Boolean.cs @@ -20,9 +20,7 @@ public static partial class Guard public static void IsTrue([DoesNotReturnIf(false)] bool value, [CallerArgumentExpression("value")] string name = "") { if (value) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsTrue(name); } @@ -38,9 +36,7 @@ public static partial class Guard public static void IsTrue([DoesNotReturnIf(false)] bool value, string name, string message) { if (value) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsTrue(name, message); } @@ -55,9 +51,7 @@ public static partial class Guard public static void IsFalse([DoesNotReturnIf(true)] bool value, [CallerArgumentExpression("value")] string name = "") { if (!value) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsFalse(name); } @@ -73,9 +67,7 @@ public static partial class Guard public static void IsFalse([DoesNotReturnIf(true)] bool value, string name, string message) { if (!value) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsFalse(name, message); } @@ -155,7 +147,8 @@ public static partial class Guard { StringBuilder builder = new(); - this.handler = new StringBuilder.AppendInterpolatedStringHandler(literalLength, formattedCount, builder); + this.handler = + new StringBuilder.AppendInterpolatedStringHandler(literalLength, formattedCount, builder); this.builder = builder; shouldAppend = true; @@ -273,7 +266,8 @@ public static partial class Guard { StringBuilder builder = new(); - this.handler = new StringBuilder.AppendInterpolatedStringHandler(literalLength, formattedCount, builder); + this.handler = + new StringBuilder.AppendInterpolatedStringHandler(literalLength, formattedCount, builder); this.builder = builder; shouldAppend = true; @@ -353,4 +347,4 @@ public static partial class Guard } } #endif -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Guard.Comparable.Generic.cs b/Source/FisheryLib/Diagnostics/Guard.Comparable.Generic.cs index 80c2ad3..2a34410 100644 --- a/Source/FisheryLib/Diagnostics/Guard.Comparable.Generic.cs +++ b/Source/FisheryLib/Diagnostics/Guard.Comparable.Generic.cs @@ -21,9 +21,7 @@ public partial class Guard where T : struct, IEquatable { if (value.Equals(default)) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsDefault(value, name); } @@ -40,9 +38,7 @@ public partial class Guard where T : struct, IEquatable { if (!value.Equals(default)) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNotDefault(name); } @@ -61,9 +57,7 @@ public partial class Guard where T : notnull, IEquatable { if (value.Equals(target)) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsEqualTo(value, target, name); } @@ -82,9 +76,7 @@ public partial class Guard where T : notnull, IEquatable { if (!value.Equals(target)) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNotEqualTo(value, target, name); } @@ -98,7 +90,8 @@ public partial class Guard /// The name of the input parameter being tested. /// Thrown if is not a bitwise match for . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void IsBitwiseEqualTo(T value, T target, [CallerArgumentExpression("value")] string name = "") + public static unsafe void IsBitwiseEqualTo(T value, T target, + [CallerArgumentExpression("value")] string name = "") where T : unmanaged { // Include some fast paths if the input type is of size 1, 2, 4, 8, or 16. @@ -112,9 +105,7 @@ public partial class Guard var targetByte = Unsafe.As(ref target); if (valueByte == targetByte) - { return; - } ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name); } @@ -124,9 +115,7 @@ public partial class Guard var targetUShort = Unsafe.As(ref target); if (valueUShort == targetUShort) - { return; - } ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name); } @@ -136,9 +125,7 @@ public partial class Guard var targetUInt = Unsafe.As(ref target); if (valueUInt == targetUInt) - { return; - } ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name); } @@ -148,9 +135,7 @@ public partial class Guard var targetULong = Unsafe.As(ref target); if (Bit64Compare(ref valueULong, ref targetULong)) - { return; - } ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name); } @@ -165,9 +150,7 @@ public partial class Guard var targetULong1 = Unsafe.Add(ref Unsafe.As(ref target), 1); if (Bit64Compare(ref valueULong1, ref targetULong1)) - { return; - } } ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name); @@ -178,9 +161,7 @@ public partial class Guard Span targetBytes = new(Unsafe.AsPointer(ref target), sizeof(T)); if (valueBytes.SequenceEqual(targetBytes)) - { return; - } ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name); } @@ -196,8 +177,7 @@ public partial class Guard ref var r0 = ref Unsafe.As(ref left); ref var r1 = ref Unsafe.As(ref right); - return r0 == r1 && - Unsafe.Add(ref r0, 1) == Unsafe.Add(ref r1, 1); + return r0 == r1 && Unsafe.Add(ref r0, 1) == Unsafe.Add(ref r1, 1); } return left == right; @@ -217,9 +197,7 @@ public partial class Guard where T : notnull, IComparable { if (value.CompareTo(maximum) < 0) - { return; - } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsLessThan(value, maximum, name); } @@ -238,9 +216,7 @@ public partial class Guard where T : notnull, IComparable { if (value.CompareTo(maximum) <= 0) - { return; - } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsLessThanOrEqualTo(value, maximum, name); } @@ -259,9 +235,7 @@ public partial class Guard where T : notnull, IComparable { if (value.CompareTo(minimum) > 0) - { return; - } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsGreaterThan(value, minimum, name); } @@ -276,13 +250,12 @@ public partial class Guard /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsGreaterThanOrEqualTo(T value, T minimum, [CallerArgumentExpression("value")] string name = "") + public static void IsGreaterThanOrEqualTo(T value, T minimum, + [CallerArgumentExpression("value")] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) >= 0) - { return; - } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsGreaterThanOrEqualTo(value, minimum, name); } @@ -305,9 +278,7 @@ public partial class Guard where T : notnull, IComparable { if (value.CompareTo(minimum) >= 0 && value.CompareTo(maximum) < 0) - { return; - } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsInRange(value, minimum, maximum, name); } @@ -326,13 +297,12 @@ public partial class Guard /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotInRange(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotInRange(T value, T minimum, T maximum, + [CallerArgumentExpression("value")] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) < 0 || value.CompareTo(maximum) >= 0) - { return; - } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotInRange(value, minimum, maximum, name); } @@ -355,9 +325,7 @@ public partial class Guard where T : notnull, IComparable { if (value.CompareTo(minimum) > 0 && value.CompareTo(maximum) < 0) - { return; - } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsBetween(value, minimum, maximum, name); } @@ -376,13 +344,12 @@ public partial class Guard /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetween(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetween(T value, T minimum, T maximum, + [CallerArgumentExpression("value")] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) <= 0 || value.CompareTo(maximum) >= 0) - { return; - } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotBetween(value, minimum, maximum, name); } @@ -401,13 +368,12 @@ public partial class Guard /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsBetweenOrEqualTo(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsBetweenOrEqualTo(T value, T minimum, T maximum, + [CallerArgumentExpression("value")] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) >= 0 && value.CompareTo(maximum) <= 0) - { return; - } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsBetweenOrEqualTo(value, minimum, maximum, name); } @@ -426,14 +392,13 @@ public partial class Guard /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotBetweenOrEqualTo(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") + public static void IsNotBetweenOrEqualTo(T value, T minimum, T maximum, + [CallerArgumentExpression("value")] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) < 0 || value.CompareTo(maximum) > 0) - { return; - } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotBetweenOrEqualTo(value, minimum, maximum, name); } -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Guard.Comparable.Numeric.cs b/Source/FisheryLib/Diagnostics/Guard.Comparable.Numeric.cs index a208ad1..be408c6 100644 --- a/Source/FisheryLib/Diagnostics/Guard.Comparable.Numeric.cs +++ b/Source/FisheryLib/Diagnostics/Guard.Comparable.Numeric.cs @@ -18,14 +18,13 @@ public partial class Guard /// The name of the input parameter being tested. /// Thrown if ( - ) > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsCloseTo(int value, int target, uint delta, [CallerArgumentExpression("value")] string name = "") + public static void IsCloseTo(int value, int target, uint delta, + [CallerArgumentExpression("value")] string name = "") { var difference = value >= target ? (uint)(value - target) : (uint)(target - value); if (difference <= delta) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsCloseTo(value, target, delta, name); } @@ -39,14 +38,13 @@ public partial class Guard /// The name of the input parameter being tested. /// Thrown if ( - ) <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotCloseTo(int value, int target, uint delta, [CallerArgumentExpression("value")] string name = "") + public static void IsNotCloseTo(int value, int target, uint delta, + [CallerArgumentExpression("value")] string name = "") { var difference = value >= target ? (uint)(value - target) : (uint)(target - value); if (difference > delta) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNotCloseTo(value, target, delta, name); } @@ -60,14 +58,13 @@ public partial class Guard /// The name of the input parameter being tested. /// Thrown if ( - ) > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsCloseTo(long value, long target, ulong delta, [CallerArgumentExpression("value")] string name = "") + public static void IsCloseTo(long value, long target, ulong delta, + [CallerArgumentExpression("value")] string name = "") { var difference = value >= target ? (ulong)(value - target) : (ulong)(target - value); if (difference <= delta) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsCloseTo(value, target, delta, name); } @@ -81,14 +78,13 @@ public partial class Guard /// The name of the input parameter being tested. /// Thrown if ( - ) <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotCloseTo(long value, long target, ulong delta, [CallerArgumentExpression("value")] string name = "") + public static void IsNotCloseTo(long value, long target, ulong delta, + [CallerArgumentExpression("value")] string name = "") { var difference = value >= target ? (ulong)(value - target) : (ulong)(target - value); if (difference > delta) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNotCloseTo(value, target, delta, name); } @@ -102,12 +98,11 @@ public partial class Guard /// The name of the input parameter being tested. /// Thrown if ( - ) > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsCloseTo(float value, float target, float delta, [CallerArgumentExpression("value")] string name = "") + public static void IsCloseTo(float value, float target, float delta, + [CallerArgumentExpression("value")] string name = "") { if (Math.Abs(value - target) <= delta) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsCloseTo(value, target, delta, name); } @@ -121,12 +116,11 @@ public partial class Guard /// The name of the input parameter being tested. /// Thrown if ( - ) <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotCloseTo(float value, float target, float delta, [CallerArgumentExpression("value")] string name = "") + public static void IsNotCloseTo(float value, float target, float delta, + [CallerArgumentExpression("value")] string name = "") { if (Math.Abs(value - target) > delta) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNotCloseTo(value, target, delta, name); } @@ -140,12 +134,11 @@ public partial class Guard /// The name of the input parameter being tested. /// Thrown if ( - ) > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsCloseTo(double value, double target, double delta, [CallerArgumentExpression("value")] string name = "") + public static void IsCloseTo(double value, double target, double delta, + [CallerArgumentExpression("value")] string name = "") { if (Math.Abs(value - target) <= delta) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsCloseTo(value, target, delta, name); } @@ -159,12 +152,11 @@ public partial class Guard /// The name of the input parameter being tested. /// Thrown if ( - ) <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotCloseTo(double value, double target, double delta, [CallerArgumentExpression("value")] string name = "") + public static void IsNotCloseTo(double value, double target, double delta, + [CallerArgumentExpression("value")] string name = "") { if (Math.Abs(value - target) > delta) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNotCloseTo(value, target, delta, name); } @@ -178,14 +170,13 @@ public partial class Guard /// The name of the input parameter being tested. /// Thrown if ( - ) > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsCloseTo(nint value, nint target, nuint delta, [CallerArgumentExpression("value")] string name = "") + public static void IsCloseTo(nint value, nint target, nuint delta, + [CallerArgumentExpression("value")] string name = "") { var difference = value >= target ? (nuint)(value - target) : (nuint)(target - value); if (difference <= delta) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsCloseTo(value, target, delta, name); } @@ -199,15 +190,14 @@ public partial class Guard /// The name of the input parameter being tested. /// Thrown if ( - ) <= . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotCloseTo(nint value, nint target, nuint delta, [CallerArgumentExpression("value")] string name = "") + public static void IsNotCloseTo(nint value, nint target, nuint delta, + [CallerArgumentExpression("value")] string name = "") { var difference = value >= target ? (nuint)(value - target) : (nuint)(target - value); if (difference > delta) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNotCloseTo(value, target, delta, name); } -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Guard.IO.cs b/Source/FisheryLib/Diagnostics/Guard.IO.cs index e645849..af21ced 100644 --- a/Source/FisheryLib/Diagnostics/Guard.IO.cs +++ b/Source/FisheryLib/Diagnostics/Guard.IO.cs @@ -20,9 +20,7 @@ public partial class Guard public static void CanRead(Stream stream, [CallerArgumentExpression("stream")] string name = "") { if (stream.CanRead) - { return; - } ThrowHelper.ThrowArgumentExceptionForCanRead(stream, name); } @@ -37,9 +35,7 @@ public partial class Guard public static void CanWrite(Stream stream, [CallerArgumentExpression("stream")] string name = "") { if (stream.CanWrite) - { return; - } ThrowHelper.ThrowArgumentExceptionForCanWrite(stream, name); } @@ -54,9 +50,7 @@ public partial class Guard public static void CanSeek(Stream stream, [CallerArgumentExpression("stream")] string name = "") { if (stream.CanSeek) - { return; - } ThrowHelper.ThrowArgumentExceptionForCanSeek(stream, name); } @@ -71,10 +65,8 @@ public partial class Guard public static void IsAtStartPosition(Stream stream, [CallerArgumentExpression("stream")] string name = "") { if (stream.Position == 0) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsAtStartPosition(stream, name); } -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Guard.String.cs b/Source/FisheryLib/Diagnostics/Guard.String.cs index ebbab5f..b8b39d0 100644 --- a/Source/FisheryLib/Diagnostics/Guard.String.cs +++ b/Source/FisheryLib/Diagnostics/Guard.String.cs @@ -22,9 +22,7 @@ public partial class Guard public static void IsNullOrEmpty(string? text, [CallerArgumentExpression("text")] string name = "") { if (string.IsNullOrEmpty(text)) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNullOrEmpty(text, name); } @@ -40,9 +38,7 @@ public partial class Guard public static void IsNotNullOrEmpty([NotNull] string? text, [CallerArgumentExpression("text")] string name = "") { if (!string.IsNullOrEmpty(text)) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNotNullOrEmpty(text, name); } @@ -57,9 +53,7 @@ public partial class Guard public static void IsNullOrWhiteSpace(string? text, [CallerArgumentExpression("text")] string name = "") { if (string.IsNullOrWhiteSpace(text)) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNullOrWhiteSpace(text, name); } @@ -72,12 +66,11 @@ public partial class Guard /// Thrown if is . /// Thrown if is whitespace. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotNullOrWhiteSpace([NotNull] string? text, [CallerArgumentExpression("text")] string name = "") + public static void IsNotNullOrWhiteSpace([NotNull] string? text, + [CallerArgumentExpression("text")] string name = "") { if (!string.IsNullOrWhiteSpace(text)) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNotNullOrWhiteSpace(text, name); } @@ -92,9 +85,7 @@ public partial class Guard public static void IsEmpty(string text, [CallerArgumentExpression("text")] string name = "") { if (text.Length == 0) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsEmpty(text, name); } @@ -109,9 +100,7 @@ public partial class Guard public static void IsNotEmpty(string text, [CallerArgumentExpression("text")] string name = "") { if (text.Length != 0) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNotEmpty(name); } @@ -126,9 +115,7 @@ public partial class Guard public static void IsWhiteSpace(string text, [CallerArgumentExpression("text")] string name = "") { if (string.IsNullOrWhiteSpace(text)) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsWhiteSpace(text, name); } @@ -143,9 +130,7 @@ public partial class Guard public static void IsNotWhiteSpace(string text, [CallerArgumentExpression("text")] string name = "") { if (!string.IsNullOrWhiteSpace(text)) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNotWhiteSpace(text, name); } @@ -161,9 +146,7 @@ public partial class Guard public static void HasSizeEqualTo(string text, int size, [CallerArgumentExpression("text")] string name = "") { if (text.Length == size) - { return; - } ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(text, size, name); } @@ -179,9 +162,7 @@ public partial class Guard public static void HasSizeNotEqualTo(string text, int size, [CallerArgumentExpression("text")] string name = "") { if (text.Length != size) - { return; - } ThrowHelper.ThrowArgumentExceptionForHasSizeNotEqualTo(text, size, name); } @@ -197,9 +178,7 @@ public partial class Guard public static void HasSizeGreaterThan(string text, int size, [CallerArgumentExpression("text")] string name = "") { if (text.Length > size) - { return; - } ThrowHelper.ThrowArgumentExceptionForHasSizeGreaterThan(text, size, name); } @@ -212,12 +191,11 @@ public partial class Guard /// The name of the input parameter being tested. /// Thrown if the size of is < . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeGreaterThanOrEqualTo(string text, int size, [CallerArgumentExpression("text")] string name = "") + public static void HasSizeGreaterThanOrEqualTo(string text, int size, + [CallerArgumentExpression("text")] string name = "") { if (text.Length >= size) - { return; - } ThrowHelper.ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo(text, size, name); } @@ -233,9 +211,7 @@ public partial class Guard public static void HasSizeLessThan(string text, int size, [CallerArgumentExpression("text")] string name = "") { if (text.Length < size) - { return; - } ThrowHelper.ThrowArgumentExceptionForHasSizeLessThan(text, size, name); } @@ -248,12 +224,11 @@ public partial class Guard /// The name of the input parameter being tested. /// Thrown if the size of is > . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(string text, int size, [CallerArgumentExpression("text")] string name = "") + public static void HasSizeLessThanOrEqualTo(string text, int size, + [CallerArgumentExpression("text")] string name = "") { if (text.Length <= size) - { return; - } ThrowHelper.ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(text, size, name); } @@ -267,12 +242,11 @@ public partial class Guard /// Thrown if the size of is != the one of . /// The type is immutable, but the name of this API is kept for consistency with the other overloads. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeEqualTo(string text, string destination, [CallerArgumentExpression("text")] string name = "") + public static void HasSizeEqualTo(string text, string destination, + [CallerArgumentExpression("text")] string name = "") { if (text.Length == destination.Length) - { return; - } ThrowHelper.ThrowArgumentExceptionForHasSizeEqualTo(text, destination, name); } @@ -286,12 +260,11 @@ public partial class Guard /// Thrown if the size of is > the one of . /// The type is immutable, but the name of this API is kept for consistency with the other overloads. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasSizeLessThanOrEqualTo(string text, string destination, [CallerArgumentExpression("text")] string name = "") + public static void HasSizeLessThanOrEqualTo(string text, string destination, + [CallerArgumentExpression("text")] string name = "") { if (text.Length <= destination.Length) - { return; - } ThrowHelper.ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(text, destination, name); } @@ -307,9 +280,7 @@ public partial class Guard public static void IsInRangeFor(int index, string text, [CallerArgumentExpression("index")] string name = "") { if ((uint)index < (uint)text.Length) - { return; - } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsInRangeFor(index, text, name); } @@ -325,10 +296,8 @@ public partial class Guard public static void IsNotInRangeFor(int index, string text, [CallerArgumentExpression("index")] string name = "") { if ((uint)index >= (uint)text.Length) - { return; - } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor(index, text, name); } -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Guard.Tasks.cs b/Source/FisheryLib/Diagnostics/Guard.Tasks.cs index ee38611..b3d611c 100644 --- a/Source/FisheryLib/Diagnostics/Guard.Tasks.cs +++ b/Source/FisheryLib/Diagnostics/Guard.Tasks.cs @@ -20,9 +20,7 @@ public partial class Guard public static void IsCompleted(Task task, [CallerArgumentExpression("task")] string name = "") { if (task.IsCompleted) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsCompleted(task, name); } @@ -37,9 +35,7 @@ public partial class Guard public static void IsNotCompleted(Task task, [CallerArgumentExpression("task")] string name = "") { if (!task.IsCompleted) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNotCompleted(task, name); } @@ -54,9 +50,7 @@ public partial class Guard public static void IsCompletedSuccessfully(Task task, [CallerArgumentExpression("task")] string name = "") { if (task.Status == TaskStatus.RanToCompletion) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsCompletedSuccessfully(task, name); } @@ -71,9 +65,7 @@ public partial class Guard public static void IsNotCompletedSuccessfully(Task task, [CallerArgumentExpression("task")] string name = "") { if (task.Status != TaskStatus.RanToCompletion) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNotCompletedSuccessfully(task, name); } @@ -88,9 +80,7 @@ public partial class Guard public static void IsFaulted(Task task, [CallerArgumentExpression("task")] string name = "") { if (task.IsFaulted) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsFaulted(task, name); } @@ -105,9 +95,7 @@ public partial class Guard public static void IsNotFaulted(Task task, [CallerArgumentExpression("task")] string name = "") { if (!task.IsFaulted) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNotFaulted(task, name); } @@ -122,9 +110,7 @@ public partial class Guard public static void IsCanceled(Task task, [CallerArgumentExpression("task")] string name = "") { if (task.IsCanceled) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsCanceled(task, name); } @@ -139,9 +125,7 @@ public partial class Guard public static void IsNotCanceled(Task task, [CallerArgumentExpression("task")] string name = "") { if (!task.IsCanceled) - { return; - } ThrowHelper.ThrowArgumentExceptionForIsNotCanceled(task, name); } @@ -154,12 +138,11 @@ public partial class Guard /// The name of the input parameter being tested. /// Thrown if doesn't match . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasStatusEqualTo(Task task, TaskStatus status, [CallerArgumentExpression("task")] string name = "") + public static void HasStatusEqualTo(Task task, TaskStatus status, + [CallerArgumentExpression("task")] string name = "") { if (task.Status == status) - { return; - } ThrowHelper.ThrowArgumentExceptionForHasStatusEqualTo(task, status, name); } @@ -172,13 +155,12 @@ public partial class Guard /// The name of the input parameter being tested. /// Thrown if matches . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void HasStatusNotEqualTo(Task task, TaskStatus status, [CallerArgumentExpression("task")] string name = "") + public static void HasStatusNotEqualTo(Task task, TaskStatus status, + [CallerArgumentExpression("task")] string name = "") { if (task.Status != status) - { return; - } ThrowHelper.ThrowArgumentExceptionForHasStatusNotEqualTo(task, status, name); } -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Guard.cs b/Source/FisheryLib/Diagnostics/Guard.cs index a3aeff4..b370723 100644 --- a/Source/FisheryLib/Diagnostics/Guard.cs +++ b/Source/FisheryLib/Diagnostics/Guard.cs @@ -3,8 +3,9 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; namespace FisheryLib.Utility.Diagnostics; @@ -12,6 +13,7 @@ namespace FisheryLib.Utility.Diagnostics; /// Helper methods to verify conditions when running code. /// [DebuggerStepThrough] +[PublicAPI] public static partial class Guard { /// @@ -22,7 +24,7 @@ public static partial class Guard /// The name of the input parameter being tested. /// Thrown if is not . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNull(T? value, [CallerArgumentExpression("value")] string name = "") + public static void IsNull([NoEnumeration] T? value, [CallerArgumentExpression("value")] string name = "") { if (value is null) return; @@ -38,7 +40,7 @@ public static partial class Guard /// The name of the input parameter being tested. /// Thrown if is not . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNull(T? value, [CallerArgumentExpression("value")] string name = "") + public static void IsNull([NoEnumeration] T? value, [CallerArgumentExpression("value")] string name = "") where T : struct { if (value is null) @@ -55,7 +57,8 @@ public static partial class Guard /// The name of the input parameter being tested. /// Thrown if is . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotNull([NotNull] T? value, [CallerArgumentExpression("value")] string name = "") + public static void IsNotNull([NoEnumeration] [NotNull] T? value, + [CallerArgumentExpression("value")] string name = "") { if (value is not null) return; @@ -71,7 +74,8 @@ public static partial class Guard /// The name of the input parameter being tested. /// Thrown if is . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotNull([NotNull] T? value, [CallerArgumentExpression("value")] string name = "") + public static void IsNotNull([NoEnumeration] [NotNull] T? value, + [CallerArgumentExpression("value")] string name = "") where T : struct { if (value is not null) @@ -88,7 +92,7 @@ public static partial class Guard /// The name of the input parameter being tested. /// Thrown if is not of type . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsOfType(object value, [CallerArgumentExpression("value")] string name = "") + public static void IsOfType([NoEnumeration] object value, [CallerArgumentExpression("value")] string name = "") { if (value.GetType() == typeof(T)) return; @@ -104,7 +108,8 @@ public static partial class Guard /// The name of the input parameter being tested. /// Thrown if is of type . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotOfType(object value, [CallerArgumentExpression("value")] string name = "") + public static void IsNotOfType([NoEnumeration] object value, + [CallerArgumentExpression("value")] string name = "") { if (value.GetType() != typeof(T)) return; @@ -120,7 +125,8 @@ public static partial class Guard /// The name of the input parameter being tested. /// Thrown if the type of is not the same as . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsOfType(object value, Type type, [CallerArgumentExpression("value")] string name = "") + public static void IsOfType([NoEnumeration] object value, Type type, + [CallerArgumentExpression("value")] string name = "") { if (value.GetType() == type) return; @@ -136,7 +142,8 @@ public static partial class Guard /// The name of the input parameter being tested. /// Thrown if the type of is the same as . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotOfType(object value, Type type, [CallerArgumentExpression("value")] string name = "") + public static void IsNotOfType([NoEnumeration] object value, Type type, + [CallerArgumentExpression("value")] string name = "") { if (value.GetType() != type) return; @@ -152,7 +159,8 @@ public static partial class Guard /// The name of the input parameter being tested. /// Thrown if can't be assigned to type . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsAssignableToType(object value, [CallerArgumentExpression("value")] string name = "") + public static void IsAssignableToType([NoEnumeration] object value, + [CallerArgumentExpression("value")] string name = "") { if (value is T) return; @@ -168,7 +176,8 @@ public static partial class Guard /// The name of the input parameter being tested. /// Thrown if can be assigned to type . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotAssignableToType(object value, [CallerArgumentExpression("value")] string name = "") + public static void IsNotAssignableToType([NoEnumeration] object value, + [CallerArgumentExpression("value")] string name = "") { if (value is not T) return; @@ -184,7 +193,8 @@ public static partial class Guard /// The name of the input parameter being tested. /// Thrown if can't be assigned to . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsAssignableToType(object value, Type type, [CallerArgumentExpression("value")] string name = "") + public static void IsAssignableToType([NoEnumeration] object value, Type type, + [CallerArgumentExpression("value")] string name = "") { if (type.IsInstanceOfType(value)) return; @@ -200,13 +210,48 @@ public static partial class Guard /// The name of the input parameter being tested. /// Thrown if can be assigned to . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsNotAssignableToType(object value, Type type, [CallerArgumentExpression("value")] string name = "") + public static void IsNotAssignableToType([NoEnumeration] object value, Type type, + [CallerArgumentExpression("value")] string name = "") { if (!type.IsInstanceOfType(value)) return; ThrowHelper.ThrowArgumentExceptionForIsNotAssignableToType(value, type, name); } + + /// + /// Asserts that the input type can be assigned to a specified type. + /// + /// The input to test. + /// The type to look for. + /// The name of the input parameter being tested. + /// Thrown if can't be assigned to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void IsTypeAssignableToType([NoEnumeration] Type value, Type type, + [CallerArgumentExpression("value")] string name = "") + { + if (type.IsAssignableFrom(value)) + return; + + ThrowHelper.ThrowArgumentExceptionForIsAssignableToType(value, type, name); + } + + /// + /// Asserts that the input type can't be assigned to a specified type. + /// + /// The input to test. + /// The type to look for. + /// The name of the input parameter being tested. + /// Thrown if can be assigned to . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void IsTypeNotAssignableToType([NoEnumeration] Type value, Type type, + [CallerArgumentExpression("value")] string name = "") + { + if (!type.IsAssignableFrom(value)) + return; + + ThrowHelper.ThrowArgumentExceptionForIsNotAssignableToType(value, type, name); + } /// /// Asserts that the input value must be the same instance as the target value. @@ -218,7 +263,8 @@ public static partial class Guard /// Thrown if is not the same instance as . /// The method is generic to prevent using it with value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsReferenceEqualTo(T value, T target, [CallerArgumentExpression("value")] string name = "") + public static void IsReferenceEqualTo([NoEnumeration] T value, T target, + [CallerArgumentExpression("value")] string name = "") where T : class { if (ReferenceEquals(value, target)) @@ -237,7 +283,8 @@ public static partial class Guard /// Thrown if is the same instance as . /// The method is generic to prevent using it with value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IsReferenceNotEqualTo(T value, T target, [CallerArgumentExpression("value")] string name = "") + public static void IsReferenceNotEqualTo([NoEnumeration] T value, T target, + [CallerArgumentExpression("value")] string name = "") where T : class { if (!ReferenceEquals(value, target)) @@ -245,4 +292,4 @@ public static partial class Guard ThrowHelper.ThrowArgumentExceptionForIsReferenceNotEqualTo(name); } -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Internals/Guard.Collection.Generic.ThrowHelper.cs b/Source/FisheryLib/Diagnostics/Internals/Guard.Collection.Generic.ThrowHelper.cs index 4d13d6d..df280e7 100644 --- a/Source/FisheryLib/Diagnostics/Internals/Guard.Collection.Generic.ThrowHelper.cs +++ b/Source/FisheryLib/Diagnostics/Internals/Guard.Collection.Generic.ThrowHelper.cs @@ -19,9 +19,8 @@ public partial class Guard /// This method is needed because can't be used as a generic type parameter. [DoesNotReturn] public static void ThrowArgumentExceptionForIsNotEmptyWithSpan(string name) - { - throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(Span).ToTypeString()}) must not be empty.", name); - } + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(Span).ToTypeString()}) must not be empty.", name); /// /// Throws an when fails. @@ -30,9 +29,8 @@ public partial class Guard /// This method is needed because can't be used as a generic type parameter. [DoesNotReturn] public static void ThrowArgumentExceptionForIsNotEmptyWithReadOnlySpan(string name) - { - throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(ReadOnlySpan).ToTypeString()}) must not be empty.", name); - } + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(ReadOnlySpan).ToTypeString()}) must not be empty.", name); /// /// Throws an when (or an overload) fails. @@ -40,8 +38,7 @@ public partial class Guard /// The item of items in the input collection. [DoesNotReturn] public static void ThrowArgumentExceptionForIsNotEmpty(string name) - { - throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be empty.", name); - } + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be empty.", name); } -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Internals/Guard.Comparable.Generic.ThrowHelper.cs b/Source/FisheryLib/Diagnostics/Internals/Guard.Comparable.Generic.ThrowHelper.cs index 2264eba..179613b 100644 --- a/Source/FisheryLib/Diagnostics/Internals/Guard.Comparable.Generic.ThrowHelper.cs +++ b/Source/FisheryLib/Diagnostics/Internals/Guard.Comparable.Generic.ThrowHelper.cs @@ -18,7 +18,10 @@ public partial class Guard /// The type of value type being tested. [DoesNotReturn] public static void ThrowArgumentExceptionForIsDefault(T value, string name) - where T : struct => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be the default value {AssertString(default(T))}, was {AssertString(value)}.", name); + where T : struct + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be the default value {AssertString(default(T))}, was {AssertString(value)}.", + name); /// /// Throws an when fails. @@ -26,21 +29,30 @@ public partial class Guard /// The type of value type being tested. [DoesNotReturn] public static void ThrowArgumentExceptionForIsNotDefault(string name) - where T : struct => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be the default value {AssertString(default(T))}.", name); + where T : struct + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be the default value {AssertString(default(T))}.", + name); /// /// Throws an when fails. /// /// The type of values being tested. [DoesNotReturn] - public static void ThrowArgumentExceptionForIsEqualTo(T value, T target, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be equal to {AssertString(target)}, was {AssertString(value)}.", name); + public static void ThrowArgumentExceptionForIsEqualTo(T value, T target, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be equal to {AssertString(target)}, was {AssertString(value)}.", + name); /// /// Throws an when fails. /// /// The type of values being tested. [DoesNotReturn] - public static void ThrowArgumentExceptionForIsNotEqualTo(T value, T target, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be equal to {AssertString(target)}, was {AssertString(value)}.", name); + public static void ThrowArgumentExceptionForIsNotEqualTo(T value, T target, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be equal to {AssertString(target)}, was {AssertString(value)}.", + name); /// /// Throws an when fails. @@ -48,76 +60,103 @@ public partial class Guard /// The type of input values being compared. [DoesNotReturn] public static void ThrowArgumentExceptionForBitwiseEqualTo(T value, T target, string name) - where T : unmanaged => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) is not a bitwise match, was <{value.ToHexString()}> instead of <{target.ToHexString()}>.", name); + where T : unmanaged + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) is not a bitwise match, was <{value.ToHexString()}> instead of <{target.ToHexString()}>.", + name); /// /// Throws an when fails. /// /// The type of values being tested. [DoesNotReturn] - public static void ThrowArgumentOutOfRangeExceptionForIsLessThan(T value, T maximum, string name) => throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be less than {AssertString(maximum)}, was {AssertString(value)}."); + public static void ThrowArgumentOutOfRangeExceptionForIsLessThan(T value, T maximum, string name) + => throw new ArgumentOutOfRangeException(name, value!, + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be less than {AssertString(maximum)}, was {AssertString(value)}."); /// /// Throws an when fails. /// /// The type of values being tested. [DoesNotReturn] - public static void ThrowArgumentOutOfRangeExceptionForIsLessThanOrEqualTo(T value, T maximum, string name) => throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be less than or equal to {AssertString(maximum)}, was {AssertString(value)}."); + public static void ThrowArgumentOutOfRangeExceptionForIsLessThanOrEqualTo(T value, T maximum, string name) + => throw new ArgumentOutOfRangeException(name, value!, + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be less than or equal to {AssertString(maximum)}, was {AssertString(value)}."); /// /// Throws an when fails. /// /// The type of values being tested. [DoesNotReturn] - public static void ThrowArgumentOutOfRangeExceptionForIsGreaterThan(T value, T minimum, string name) => throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be greater than {AssertString(minimum)}, was {AssertString(value)}."); + public static void ThrowArgumentOutOfRangeExceptionForIsGreaterThan(T value, T minimum, string name) + => throw new ArgumentOutOfRangeException(name, value!, + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be greater than {AssertString(minimum)}, was {AssertString(value)}."); /// /// Throws an when fails. /// /// The type of values being tested. [DoesNotReturn] - public static void ThrowArgumentOutOfRangeExceptionForIsGreaterThanOrEqualTo(T value, T minimum, string name) => throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be greater than or equal to {AssertString(minimum)}, was {AssertString(value)}."); + public static void ThrowArgumentOutOfRangeExceptionForIsGreaterThanOrEqualTo(T value, T minimum, string name) + => throw new ArgumentOutOfRangeException(name, value!, + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be greater than or equal to {AssertString(minimum)}, was {AssertString(value)}."); /// /// Throws an when fails. /// /// The type of values being tested. [DoesNotReturn] - public static void ThrowArgumentOutOfRangeExceptionForIsInRange(T value, T minimum, T maximum, string name) => throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be in the range given by {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}."); + public static void ThrowArgumentOutOfRangeExceptionForIsInRange(T value, T minimum, T maximum, string name) + => throw new ArgumentOutOfRangeException(name, value!, + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be in the range given by {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}."); /// /// Throws an when fails. /// /// The type of values being tested. [DoesNotReturn] - public static void ThrowArgumentOutOfRangeExceptionForIsNotInRange(T value, T minimum, T maximum, string name) => throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be in the range given by {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}."); + public static void + ThrowArgumentOutOfRangeExceptionForIsNotInRange(T value, T minimum, T maximum, string name) + => throw new ArgumentOutOfRangeException(name, value!, + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be in the range given by {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}."); /// /// Throws an when fails. /// /// The type of values being tested. [DoesNotReturn] - public static void ThrowArgumentOutOfRangeExceptionForIsBetween(T value, T minimum, T maximum, string name) => throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be between {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}."); + public static void ThrowArgumentOutOfRangeExceptionForIsBetween(T value, T minimum, T maximum, string name) + => throw new ArgumentOutOfRangeException(name, value!, + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be between {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}."); /// /// Throws an when fails. /// /// The type of values being tested. [DoesNotReturn] - public static void ThrowArgumentOutOfRangeExceptionForIsNotBetween(T value, T minimum, T maximum, string name) => throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be between {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}."); + public static void + ThrowArgumentOutOfRangeExceptionForIsNotBetween(T value, T minimum, T maximum, string name) + => throw new ArgumentOutOfRangeException(name, value!, + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be between {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}."); /// /// Throws an when fails. /// /// The type of values being tested. [DoesNotReturn] - public static void ThrowArgumentOutOfRangeExceptionForIsBetweenOrEqualTo(T value, T minimum, T maximum, string name) => throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be between or equal to {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}."); + public static void + ThrowArgumentOutOfRangeExceptionForIsBetweenOrEqualTo(T value, T minimum, T maximum, string name) + => throw new ArgumentOutOfRangeException(name, value!, + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be between or equal to {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}."); /// /// Throws an when fails. /// /// The type of values being tested. [DoesNotReturn] - public static void ThrowArgumentOutOfRangeExceptionForIsNotBetweenOrEqualTo(T value, T minimum, T maximum, string name) => throw new ArgumentOutOfRangeException(name, value!, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be between or equal to {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}."); + public static void + ThrowArgumentOutOfRangeExceptionForIsNotBetweenOrEqualTo(T value, T minimum, T maximum, string name) + => throw new ArgumentOutOfRangeException(name, value!, + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be between or equal to {AssertString(minimum)} and {AssertString(maximum)}, was {AssertString(value)}."); } -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Internals/Guard.Comparable.Numeric.ThrowHelper.cs b/Source/FisheryLib/Diagnostics/Internals/Guard.Comparable.Numeric.ThrowHelper.cs index 4810078..b37fd62 100644 --- a/Source/FisheryLib/Diagnostics/Internals/Guard.Comparable.Numeric.ThrowHelper.cs +++ b/Source/FisheryLib/Diagnostics/Internals/Guard.Comparable.Numeric.ThrowHelper.cs @@ -16,60 +16,90 @@ public partial class Guard /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsCloseTo(int value, int target, uint delta, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(int).ToTypeString()}) must be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs((double)((long)value - target)))}.", name); + public static void ThrowArgumentExceptionForIsCloseTo(int value, int target, uint delta, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(int).ToTypeString()}) must be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs((double)((long)value - target)))}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsNotCloseTo(int value, int target, uint delta, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(int).ToTypeString()}) must not be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs((double)((long)value - target)))}.", name); + public static void ThrowArgumentExceptionForIsNotCloseTo(int value, int target, uint delta, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(int).ToTypeString()}) must not be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs((double)((long)value - target)))}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsCloseTo(long value, long target, ulong delta, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(long).ToTypeString()}) must be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs((decimal)value - target))}.", name); + public static void ThrowArgumentExceptionForIsCloseTo(long value, long target, ulong delta, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(long).ToTypeString()}) must be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs((decimal)value - target))}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsNotCloseTo(long value, long target, ulong delta, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(long).ToTypeString()}) must not be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs((decimal)value - target))}.", name); + public static void ThrowArgumentExceptionForIsNotCloseTo(long value, long target, ulong delta, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(long).ToTypeString()}) must not be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs((decimal)value - target))}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsCloseTo(float value, float target, float delta, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(float).ToTypeString()}) must be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}.", name); + public static void ThrowArgumentExceptionForIsCloseTo(float value, float target, float delta, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(float).ToTypeString()}) must be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsNotCloseTo(float value, float target, float delta, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(float).ToTypeString()}) must not be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}.", name); + public static void ThrowArgumentExceptionForIsNotCloseTo(float value, float target, float delta, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(float).ToTypeString()}) must not be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsCloseTo(double value, double target, double delta, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(double).ToTypeString()}) must be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}.", name); + public static void ThrowArgumentExceptionForIsCloseTo(double value, double target, double delta, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(double).ToTypeString()}) must be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsNotCloseTo(double value, double target, double delta, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(double).ToTypeString()}) must not be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}.", name); + public static void ThrowArgumentExceptionForIsNotCloseTo(double value, double target, double delta, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(double).ToTypeString()}) must not be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsCloseTo(nint value, nint target, nuint delta, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(nint).ToTypeString()}) must be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}.", name); + public static void ThrowArgumentExceptionForIsCloseTo(nint value, nint target, nuint delta, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(nint).ToTypeString()}) must be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsNotCloseTo(nint value, nint target, nuint delta, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(nint).ToTypeString()}) must not be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}.", name); + public static void ThrowArgumentExceptionForIsNotCloseTo(nint value, nint target, nuint delta, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(nint).ToTypeString()}) must not be within a distance of {AssertString(delta)} from {AssertString(target)}, was {AssertString(value)} and had a distance of {AssertString(Math.Abs(value - target))}.", + name); } -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Internals/Guard.IO.ThrowHelper.cs b/Source/FisheryLib/Diagnostics/Internals/Guard.IO.ThrowHelper.cs index 9053433..165861b 100644 --- a/Source/FisheryLib/Diagnostics/Internals/Guard.IO.ThrowHelper.cs +++ b/Source/FisheryLib/Diagnostics/Internals/Guard.IO.ThrowHelper.cs @@ -17,24 +17,33 @@ public partial class Guard /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForCanRead(Stream stream, string name) => throw new ArgumentException($"Stream {AssertString(name)} ({stream.GetType().ToTypeString()}) doesn't support reading.", name); + public static void ThrowArgumentExceptionForCanRead(Stream stream, string name) + => throw new ArgumentException( + $"Stream {AssertString(name)} ({stream.GetType().ToTypeString()}) doesn't support reading.", name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForCanWrite(Stream stream, string name) => throw new ArgumentException($"Stream {AssertString(name)} ({stream.GetType().ToTypeString()}) doesn't support writing.", name); + public static void ThrowArgumentExceptionForCanWrite(Stream stream, string name) + => throw new ArgumentException( + $"Stream {AssertString(name)} ({stream.GetType().ToTypeString()}) doesn't support writing.", name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForCanSeek(Stream stream, string name) => throw new ArgumentException($"Stream {AssertString(name)} ({stream.GetType().ToTypeString()}) doesn't support seeking.", name); + public static void ThrowArgumentExceptionForCanSeek(Stream stream, string name) + => throw new ArgumentException( + $"Stream {AssertString(name)} ({stream.GetType().ToTypeString()}) doesn't support seeking.", name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsAtStartPosition(Stream stream, string name) => throw new ArgumentException($"Stream {AssertString(name)} ({stream.GetType().ToTypeString()}) must be at position {AssertString(0)}, was at {AssertString(stream.Position)}.", name); + public static void ThrowArgumentExceptionForIsAtStartPosition(Stream stream, string name) + => throw new ArgumentException( + $"Stream {AssertString(name)} ({stream.GetType().ToTypeString()}) must be at position {AssertString(0)}, was at {AssertString(stream.Position)}.", + name); } -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Internals/Guard.String.ThrowHelper.cs b/Source/FisheryLib/Diagnostics/Internals/Guard.String.ThrowHelper.cs index 0ff9c6e..0ec8b94 100644 --- a/Source/FisheryLib/Diagnostics/Internals/Guard.String.ThrowHelper.cs +++ b/Source/FisheryLib/Diagnostics/Internals/Guard.String.ThrowHelper.cs @@ -18,7 +18,8 @@ public partial class Guard /// [DoesNotReturn] public static void ThrowArgumentExceptionForIsNullOrEmpty(string? text, string name) - => throw new ArgumentException($"Parameter {AssertString(name)} (string) must be null or empty, was {AssertString(text)}.", name); + => throw new ArgumentException( + $"Parameter {AssertString(name)} (string) must be null or empty, was {AssertString(text)}.", name); /// /// Throws an or when fails. @@ -29,8 +30,10 @@ public partial class Guard [MethodImpl(MethodImplOptions.NoInlining)] static Exception GetException(string? text, string name) => text is null - ? new ArgumentNullException(name, $"Parameter {AssertString(name)} (string) must not be null or empty, was null.") - : new ArgumentException($"Parameter {AssertString(name)} (string) must not be null or empty, was empty.", name); + ? new ArgumentNullException(name, + $"Parameter {AssertString(name)} (string) must not be null or empty, was null.") + : new ArgumentException( + $"Parameter {AssertString(name)} (string) must not be null or empty, was empty.", name); throw GetException(text, name); } @@ -40,7 +43,8 @@ public partial class Guard /// [DoesNotReturn] public static void ThrowArgumentExceptionForIsNullOrWhiteSpace(string? text, string name) - => throw new ArgumentException($"Parameter {AssertString(name)} (string) must be null or whitespace, was {AssertString(text)}.", name); + => throw new ArgumentException( + $"Parameter {AssertString(name)} (string) must be null or whitespace, was {AssertString(text)}.", name); /// /// Throws an when fails. @@ -51,8 +55,11 @@ public partial class Guard [MethodImpl(MethodImplOptions.NoInlining)] static Exception GetException(string? text, string name) => text is null - ? new ArgumentNullException(name, $"Parameter {AssertString(name)} (string) must not be null or whitespace, was null.") - : new ArgumentException($"Parameter {AssertString(name)} (string) must not be null or whitespace, was whitespace.", name); + ? new ArgumentNullException(name, + $"Parameter {AssertString(name)} (string) must not be null or whitespace, was null.") + : new ArgumentException( + $"Parameter {AssertString(name)} (string) must not be null or whitespace, was whitespace.", + name); throw GetException(text, name); } @@ -62,7 +69,8 @@ public partial class Guard /// [DoesNotReturn] public static void ThrowArgumentExceptionForIsEmpty(string text, string name) - => throw new ArgumentException($"Parameter {AssertString(name)} (string) must be empty, was {AssertString(text)}.", name); + => throw new ArgumentException( + $"Parameter {AssertString(name)} (string) must be empty, was {AssertString(text)}.", name); /// /// Throws an when fails. @@ -76,83 +84,104 @@ public partial class Guard /// [DoesNotReturn] public static void ThrowArgumentExceptionForIsWhiteSpace(string text, string name) - => throw new ArgumentException($"Parameter {AssertString(name)} (string) must be whitespace, was {AssertString(text)}.", name); + => throw new ArgumentException( + $"Parameter {AssertString(name)} (string) must be whitespace, was {AssertString(text)}.", name); /// /// Throws an when fails. /// [DoesNotReturn] public static void ThrowArgumentExceptionForIsNotWhiteSpace(string text, string name) - => throw new ArgumentException($"Parameter {AssertString(name)} (string) must not be whitespace, was {AssertString(text)}.", name); + => throw new ArgumentException( + $"Parameter {AssertString(name)} (string) must not be whitespace, was {AssertString(text)}.", name); /// /// Throws an when fails. /// [DoesNotReturn] public static void ThrowArgumentExceptionForHasSizeEqualTo(string text, int size, string name) - => throw new ArgumentException($"Parameter {AssertString(name)} (string) must have a size equal to {size}, had a size of {text.Length} and was {AssertString(text)}.", name); + => throw new ArgumentException( + $"Parameter {AssertString(name)} (string) must have a size equal to {size}, had a size of {text.Length} and was {AssertString(text)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] public static void ThrowArgumentExceptionForHasSizeNotEqualTo(string text, int size, string name) - => throw new ArgumentException($"Parameter {AssertString(name)} (string) must not have a size equal to {size}, was {AssertString(text)}.", name); + => throw new ArgumentException( + $"Parameter {AssertString(name)} (string) must not have a size equal to {size}, was {AssertString(text)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] public static void ThrowArgumentExceptionForHasSizeGreaterThan(string text, int size, string name) - => throw new ArgumentException($"Parameter {AssertString(name)} (string) must have a size over {size}, had a size of {text.Length} and was {AssertString(text)}.", name); + => throw new ArgumentException( + $"Parameter {AssertString(name)} (string) must have a size over {size}, had a size of {text.Length} and was {AssertString(text)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] public static void ThrowArgumentExceptionForHasSizeGreaterThanOrEqualTo(string text, int size, string name) - => throw new ArgumentException($"Parameter {AssertString(name)} (string) must have a size of at least {size}, had a size of {text.Length} and was {AssertString(text)}.", name); + => throw new ArgumentException( + $"Parameter {AssertString(name)} (string) must have a size of at least {size}, had a size of {text.Length} and was {AssertString(text)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] public static void ThrowArgumentExceptionForHasSizeLessThan(string text, int size, string name) - => throw new ArgumentException($"Parameter {AssertString(name)} (string) must have a size less than {size}, had a size of {text.Length} and was {AssertString(text)}.", name); + => throw new ArgumentException( + $"Parameter {AssertString(name)} (string) must have a size less than {size}, had a size of {text.Length} and was {AssertString(text)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(string text, int size, string name) - => throw new ArgumentException($"Parameter {AssertString(name)} (string) must have a size less than or equal to {size}, had a size of {text.Length} and was {AssertString(text)}.", name); + => throw new ArgumentException( + $"Parameter {AssertString(name)} (string) must have a size less than or equal to {size}, had a size of {text.Length} and was {AssertString(text)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] public static void ThrowArgumentExceptionForHasSizeEqualTo(string source, string destination, string name) - => throw new ArgumentException($"The source {AssertString(name)} (string) must have a size equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}.", name); + => throw new ArgumentException( + $"The source {AssertString(name)} (string) must have a size equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(string source, string destination, string name) - => throw new ArgumentException($"The source {AssertString(name)} (string) must have a size less than or equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}.", name); + public static void + ThrowArgumentExceptionForHasSizeLessThanOrEqualTo(string source, string destination, string name) + => throw new ArgumentException( + $"The source {AssertString(name)} (string) must have a size less than or equal to {AssertString(destination.Length)} (the destination), had a size of {AssertString(source.Length)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] public static void ThrowArgumentOutOfRangeExceptionForIsInRangeFor(int index, string text, string name) - => throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must be in the range given by <0> and {AssertString(text.Length)} to be a valid index for the target string, was {AssertString(index)}."); + => throw new ArgumentOutOfRangeException(name, index, + $"Parameter {AssertString(name)} (int) must be in the range given by <0> and {AssertString(text.Length)} to be a valid index for the target string, was {AssertString(index)}."); /// /// Throws an when fails. /// [DoesNotReturn] public static void ThrowArgumentOutOfRangeExceptionForIsNotInRangeFor(int index, string text, string name) - => throw new ArgumentOutOfRangeException(name, index, $"Parameter {AssertString(name)} (int) must not be in the range given by <0> and {AssertString(text.Length)} to be an invalid index for the target string, was {AssertString(index)}."); + => throw new ArgumentOutOfRangeException(name, index, + $"Parameter {AssertString(name)} (int) must not be in the range given by <0> and {AssertString(text.Length)} to be an invalid index for the target string, was {AssertString(index)}."); } -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Internals/Guard.Tasks.ThrowHelper.cs b/Source/FisheryLib/Diagnostics/Internals/Guard.Tasks.ThrowHelper.cs index 4f46fab..f0a7469 100644 --- a/Source/FisheryLib/Diagnostics/Internals/Guard.Tasks.ThrowHelper.cs +++ b/Source/FisheryLib/Diagnostics/Internals/Guard.Tasks.ThrowHelper.cs @@ -17,60 +17,90 @@ public partial class Guard /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsCompleted(Task task, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must be completed, had status {AssertString(task.Status)}.", name); + public static void ThrowArgumentExceptionForIsCompleted(Task task, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must be completed, had status {AssertString(task.Status)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsNotCompleted(Task task, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must not be completed, had status {AssertString(task.Status)}.", name); + public static void ThrowArgumentExceptionForIsNotCompleted(Task task, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must not be completed, had status {AssertString(task.Status)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsCompletedSuccessfully(Task task, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must be completed successfully, had status {AssertString(task.Status)}.", name); + public static void ThrowArgumentExceptionForIsCompletedSuccessfully(Task task, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must be completed successfully, had status {AssertString(task.Status)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsNotCompletedSuccessfully(Task task, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must not be completed successfully, had status {AssertString(task.Status)}.", name); + public static void ThrowArgumentExceptionForIsNotCompletedSuccessfully(Task task, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must not be completed successfully, had status {AssertString(task.Status)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsFaulted(Task task, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must be faulted, had status {AssertString(task.Status)}.", name); + public static void ThrowArgumentExceptionForIsFaulted(Task task, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must be faulted, had status {AssertString(task.Status)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsNotFaulted(Task task, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must not be faulted, had status {AssertString(task.Status)}.", name); + public static void ThrowArgumentExceptionForIsNotFaulted(Task task, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must not be faulted, had status {AssertString(task.Status)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsCanceled(Task task, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must be canceled, had status {AssertString(task.Status)}.", name); + public static void ThrowArgumentExceptionForIsCanceled(Task task, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must be canceled, had status {AssertString(task.Status)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsNotCanceled(Task task, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must not be canceled, had status {AssertString(task.Status)}.", name); + public static void ThrowArgumentExceptionForIsNotCanceled(Task task, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must not be canceled, had status {AssertString(task.Status)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForHasStatusEqualTo(Task task, TaskStatus status, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must have status {status}, had status {AssertString(task.Status)}.", name); + public static void ThrowArgumentExceptionForHasStatusEqualTo(Task task, TaskStatus status, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must have status {status}, had status {AssertString(task.Status)}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForHasStatusNotEqualTo(Task task, TaskStatus status, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must not have status {AssertString(status)}.", name); + public static void ThrowArgumentExceptionForHasStatusNotEqualTo(Task task, TaskStatus status, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({task.GetType().ToTypeString()}) must not have status {AssertString(status)}.", + name); } -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/Internals/Guard.ThrowHelper.cs b/Source/FisheryLib/Diagnostics/Internals/Guard.ThrowHelper.cs index df45c9b..4bc296d 100644 --- a/Source/FisheryLib/Diagnostics/Internals/Guard.ThrowHelper.cs +++ b/Source/FisheryLib/Diagnostics/Internals/Guard.ThrowHelper.cs @@ -32,7 +32,10 @@ public partial class Guard /// /// The type of the input value. [DoesNotReturn] - public static void ThrowArgumentExceptionForIsNull(T value, string name) => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be null, was {AssertString(value)} ({value!.GetType().ToTypeString()}).", name); + public static void ThrowArgumentExceptionForIsNull(T value, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be null, was {AssertString(value)} ({value!.GetType().ToTypeString()}).", + name); /// /// Throws an when (where is ) fails. @@ -40,66 +43,95 @@ public partial class Guard /// The type of the input value. [DoesNotReturn] public static void ThrowArgumentExceptionForIsNull(T? value, string name) - where T : struct => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T?).ToTypeString()}) must be null, was {AssertString(value)} ({typeof(T).ToTypeString()}).", name); + where T : struct + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(T?).ToTypeString()}) must be null, was {AssertString(value)} ({typeof(T).ToTypeString()}).", + name); /// /// Throws an when fails. /// /// The type of the input value. [DoesNotReturn] - public static void ThrowArgumentNullExceptionForIsNotNull(string name) => throw new ArgumentNullException(name, $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be not null)."); + public static void ThrowArgumentNullExceptionForIsNotNull(string name) + => throw new ArgumentNullException(name, + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be not null)."); /// /// Throws an when fails. /// /// The type of the input value. [DoesNotReturn] - public static void ThrowArgumentExceptionForIsOfType(object value, string name) => throw new ArgumentException($"Parameter {AssertString(name)} must be of type {typeof(T).ToTypeString()}, was {value.GetType().ToTypeString()}.", name); + public static void ThrowArgumentExceptionForIsOfType(object value, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} must be of type {typeof(T).ToTypeString()}, was {value.GetType().ToTypeString()}.", + name); /// /// Throws an when fails. /// /// The type of the input value. [DoesNotReturn] - public static void ThrowArgumentExceptionForIsNotOfType(object value, string name) => throw new ArgumentException($"Parameter {AssertString(name)} must not be of type {typeof(T).ToTypeString()}, was {value.GetType().ToTypeString()}.", name); + public static void ThrowArgumentExceptionForIsNotOfType(object value, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} must not be of type {typeof(T).ToTypeString()}, was {value.GetType().ToTypeString()}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsOfType(object value, Type type, string name) => throw new ArgumentException($"Parameter {AssertString(name)} must be of type {type.ToTypeString()}, was {value.GetType().ToTypeString()}.", name); + public static void ThrowArgumentExceptionForIsOfType(object value, Type type, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} must be of type {type.ToTypeString()}, was {value.GetType().ToTypeString()}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsNotOfType(object value, Type type, string name) => throw new ArgumentException($"Parameter {AssertString(name)} must not be of type {type.ToTypeString()}, was {value.GetType().ToTypeString()}.", name); + public static void ThrowArgumentExceptionForIsNotOfType(object value, Type type, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} must not be of type {type.ToTypeString()}, was {value.GetType().ToTypeString()}.", + name); /// /// Throws an when fails. /// /// The type being checked against. [DoesNotReturn] - public static void ThrowArgumentExceptionForIsAssignableToType(object value, string name) => throw new ArgumentException($"Parameter {AssertString(name)} must be assignable to type {typeof(T).ToTypeString()}, was {value.GetType().ToTypeString()}.", name); + public static void ThrowArgumentExceptionForIsAssignableToType(object value, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} must be assignable to type {typeof(T).ToTypeString()}, was {value.GetType().ToTypeString()}.", + name); /// /// Throws an when fails. /// /// The type being checked against. [DoesNotReturn] - public static void ThrowArgumentExceptionForIsNotAssignableToType(object value, string name) => throw new ArgumentException($"Parameter {AssertString(name)} must not be assignable to type {typeof(T).ToTypeString()}, was {value.GetType().ToTypeString()}.", name); + public static void ThrowArgumentExceptionForIsNotAssignableToType(object value, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} must not be assignable to type {typeof(T).ToTypeString()}, was {value.GetType().ToTypeString()}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsAssignableToType(object value, Type type, string name) => throw new ArgumentException($"Parameter {AssertString(name)} must be assignable to type {type.ToTypeString()}, was {value.GetType().ToTypeString()}.", name); + public static void ThrowArgumentExceptionForIsAssignableToType(object value, Type type, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} must be assignable to type {type.ToTypeString()}, was {value.GetType().ToTypeString()}.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsNotAssignableToType(object value, Type type, string name) => throw new ArgumentException($"Parameter {AssertString(name)} must not be assignable to type {type.ToTypeString()}, was {value.GetType().ToTypeString()}.", name); + public static void ThrowArgumentExceptionForIsNotAssignableToType(object value, Type type, string name) + => throw new ArgumentException( + $"Parameter {AssertString(name)} must not be assignable to type {type.ToTypeString()}, was {value.GetType().ToTypeString()}.", + name); /// /// Throws an when fails. @@ -107,7 +139,10 @@ public partial class Guard /// The type of input value being compared. [DoesNotReturn] public static void ThrowArgumentExceptionForIsReferenceEqualTo(string name) - where T : class => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be the same instance as the target object.", name); + where T : class + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must be the same instance as the target object.", + name); /// /// Throws an when fails. @@ -115,30 +150,39 @@ public partial class Guard /// The type of input value being compared. [DoesNotReturn] public static void ThrowArgumentExceptionForIsReferenceNotEqualTo(string name) - where T : class => throw new ArgumentException($"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be the same instance as the target object.", name); + where T : class + => throw new ArgumentException( + $"Parameter {AssertString(name)} ({typeof(T).ToTypeString()}) must not be the same instance as the target object.", + name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsTrue(string name) => throw new ArgumentException($"Parameter {AssertString(name)} must be true, was false.", name); + public static void ThrowArgumentExceptionForIsTrue(string name) + => throw new ArgumentException($"Parameter {AssertString(name)} must be true, was false.", name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsTrue(string name, string message) => throw new ArgumentException($"Parameter {AssertString(name)} must be true, was false: {AssertString(message)}.", name); + public static void ThrowArgumentExceptionForIsTrue(string name, string message) + => throw new ArgumentException( + $"Parameter {AssertString(name)} must be true, was false: {AssertString(message)}.", name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsFalse(string name) => throw new ArgumentException($"Parameter {AssertString(name)} must be false, was true.", name); + public static void ThrowArgumentExceptionForIsFalse(string name) + => throw new ArgumentException($"Parameter {AssertString(name)} must be false, was true.", name); /// /// Throws an when fails. /// [DoesNotReturn] - public static void ThrowArgumentExceptionForIsFalse(string name, string message) => throw new ArgumentException($"Parameter {AssertString(name)} must be false, was true: {AssertString(message)}.", name); + public static void ThrowArgumentExceptionForIsFalse(string name, string message) + => throw new ArgumentException( + $"Parameter {AssertString(name)} must be false, was true: {AssertString(message)}.", name); } -} +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/ThrowHelper.Generic.cs b/Source/FisheryLib/Diagnostics/ThrowHelper.Generic.cs index e04a2b7..202eace 100644 --- a/Source/FisheryLib/Diagnostics/ThrowHelper.Generic.cs +++ b/Source/FisheryLib/Diagnostics/ThrowHelper.Generic.cs @@ -30,7 +30,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowArrayTypeMismatchException(string? message) => throw new ArrayTypeMismatchException(message); + public static T ThrowArrayTypeMismatchException(string? message) + => throw new ArrayTypeMismatchException(message); /// /// Throws a new . @@ -41,7 +42,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowArrayTypeMismatchException(string? message, Exception? innerException) => throw new ArrayTypeMismatchException(message, innerException); + public static T ThrowArrayTypeMismatchException(string? message, Exception? innerException) + => throw new ArrayTypeMismatchException(message, innerException); /// /// Throws a new . @@ -71,7 +73,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowArgumentException(string? message, Exception? innerException) => throw new ArgumentException(message, innerException); + public static T ThrowArgumentException(string? message, Exception? innerException) + => throw new ArgumentException(message, innerException); /// /// Throws a new . @@ -82,7 +85,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowArgumentException(string? name, string? message) => throw new ArgumentException(message, name); + public static T ThrowArgumentException(string? name, string? message) + => throw new ArgumentException(message, name); /// /// Throws a new . @@ -94,7 +98,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowArgumentException(string? name, string? message, Exception? innerException) => throw new ArgumentException(message, name, innerException); + public static T ThrowArgumentException(string? name, string? message, Exception? innerException) + => throw new ArgumentException(message, name, innerException); /// /// Throws a new . @@ -124,7 +129,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowArgumentNullException(string? name, Exception? innerException) => throw new ArgumentNullException(name, innerException); + public static T ThrowArgumentNullException(string? name, Exception? innerException) + => throw new ArgumentNullException(name, innerException); /// /// Throws a new . @@ -135,7 +141,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowArgumentNullException(string? name, string? message) => throw new ArgumentNullException(name, message); + public static T ThrowArgumentNullException(string? name, string? message) + => throw new ArgumentNullException(name, message); /// /// Throws a new . @@ -165,7 +172,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowArgumentOutOfRangeException(string? name, Exception? innerException) => throw new ArgumentOutOfRangeException(name, innerException); + public static T ThrowArgumentOutOfRangeException(string? name, Exception? innerException) + => throw new ArgumentOutOfRangeException(name, innerException); /// /// Throws a new . @@ -176,7 +184,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowArgumentOutOfRangeException(string? name, string? message) => throw new ArgumentOutOfRangeException(name, message); + public static T ThrowArgumentOutOfRangeException(string? name, string? message) + => throw new ArgumentOutOfRangeException(name, message); /// /// Throws a new . @@ -188,7 +197,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowArgumentOutOfRangeException(string? name, object? value, string? message) => throw new ArgumentOutOfRangeException(name, value, message); + public static T ThrowArgumentOutOfRangeException(string? name, object? value, string? message) + => throw new ArgumentOutOfRangeException(name, value, message); /// /// Throws a new . @@ -218,7 +228,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowCOMException(string? message, Exception? innerException) => throw new COMException(message, innerException); + public static T ThrowCOMException(string? message, Exception? innerException) + => throw new COMException(message, innerException); /// /// Throws a new . @@ -259,7 +270,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowExternalException(string? message, Exception? innerException) => throw new ExternalException(message, innerException); + public static T ThrowExternalException(string? message, Exception? innerException) + => throw new ExternalException(message, innerException); /// /// Throws a new . @@ -270,7 +282,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowExternalException(string? message, int error) => throw new ExternalException(message, error); + public static T ThrowExternalException(string? message, int error) + => throw new ExternalException(message, error); /// /// Throws a new . @@ -300,7 +313,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowFormatException(string? message, Exception? innerException) => throw new FormatException(message, innerException); + public static T ThrowFormatException(string? message, Exception? innerException) + => throw new FormatException(message, innerException); /// /// Throws a new . @@ -319,7 +333,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowInsufficientMemoryException(string? message) => throw new InsufficientMemoryException(message); + public static T ThrowInsufficientMemoryException(string? message) + => throw new InsufficientMemoryException(message); /// /// Throws a new . @@ -330,7 +345,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowInsufficientMemoryException(string? message, Exception? innerException) => throw new InsufficientMemoryException(message, innerException); + public static T ThrowInsufficientMemoryException(string? message, Exception? innerException) + => throw new InsufficientMemoryException(message, innerException); /// /// Throws a new . @@ -360,7 +376,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowInvalidDataException(string? message, Exception? innerException) => throw new InvalidDataException(message, innerException); + public static T ThrowInvalidDataException(string? message, Exception? innerException) + => throw new InvalidDataException(message, innerException); /// /// Throws a new . @@ -390,7 +407,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowInvalidOperationException(string? message, Exception? innerException) => throw new InvalidOperationException(message, innerException); + public static T ThrowInvalidOperationException(string? message, Exception? innerException) + => throw new InvalidOperationException(message, innerException); /// /// Throws a new . @@ -420,7 +438,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowLockRecursionException(string? message, Exception? innerException) => throw new LockRecursionException(message, innerException); + public static T ThrowLockRecursionException(string? message, Exception? innerException) + => throw new LockRecursionException(message, innerException); /// /// Throws a new . @@ -450,7 +469,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowMissingFieldException(string? message, Exception? innerException) => throw new MissingFieldException(message, innerException); + public static T ThrowMissingFieldException(string? message, Exception? innerException) + => throw new MissingFieldException(message, innerException); /// /// Throws a new . @@ -461,7 +481,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowMissingFieldException(string? className, string? fieldName) => throw new MissingFieldException(className, fieldName); + public static T ThrowMissingFieldException(string? className, string? fieldName) + => throw new MissingFieldException(className, fieldName); /// /// Throws a new . @@ -491,7 +512,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowMissingMemberException(string? message, Exception? innerException) => throw new MissingMemberException(message, innerException); + public static T ThrowMissingMemberException(string? message, Exception? innerException) + => throw new MissingMemberException(message, innerException); /// /// Throws a new . @@ -502,7 +524,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowMissingMemberException(string? className, string? memberName) => throw new MissingMemberException(className, memberName); + public static T ThrowMissingMemberException(string? className, string? memberName) + => throw new MissingMemberException(className, memberName); /// /// Throws a new . @@ -532,7 +555,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowMissingMethodException(string? message, Exception? innerException) => throw new MissingMethodException(message, innerException); + public static T ThrowMissingMethodException(string? message, Exception? innerException) + => throw new MissingMethodException(message, innerException); /// /// Throws a new . @@ -543,7 +567,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowMissingMethodException(string? className, string? methodName) => throw new MissingMethodException(className, methodName); + public static T ThrowMissingMethodException(string? className, string? methodName) + => throw new MissingMethodException(className, methodName); /// /// Throws a new . @@ -573,7 +598,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowNotSupportedException(string? message, Exception? innerException) => throw new NotSupportedException(message, innerException); + public static T ThrowNotSupportedException(string? message, Exception? innerException) + => throw new NotSupportedException(message, innerException); /// /// Throws a new . @@ -583,7 +609,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowObjectDisposedException(string? objectName) => throw new ObjectDisposedException(objectName); + public static T ThrowObjectDisposedException(string? objectName) + => throw new ObjectDisposedException(objectName); /// /// Throws a new . @@ -594,7 +621,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowObjectDisposedException(string? objectName, Exception? innerException) => throw new ObjectDisposedException(objectName, innerException); + public static T ThrowObjectDisposedException(string? objectName, Exception? innerException) + => throw new ObjectDisposedException(objectName, innerException); /// /// Throws a new . @@ -605,7 +633,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowObjectDisposedException(string? objectName, string? message) => throw new ObjectDisposedException(objectName, message); + public static T ThrowObjectDisposedException(string? objectName, string? message) + => throw new ObjectDisposedException(objectName, message); /// /// Throws a new . @@ -624,7 +653,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowOperationCanceledException(string? message) => throw new OperationCanceledException(message); + public static T ThrowOperationCanceledException(string? message) + => throw new OperationCanceledException(message); /// /// Throws a new . @@ -635,7 +665,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowOperationCanceledException(string? message, Exception? innerException) => throw new OperationCanceledException(message, innerException); + public static T ThrowOperationCanceledException(string? message, Exception? innerException) + => throw new OperationCanceledException(message, innerException); /// /// Throws a new . @@ -645,7 +676,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowOperationCanceledException(CancellationToken token) => throw new OperationCanceledException(token); + public static T ThrowOperationCanceledException(CancellationToken token) + => throw new OperationCanceledException(token); /// /// Throws a new . @@ -656,7 +688,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowOperationCanceledException(string? message, CancellationToken token) => throw new OperationCanceledException(message, token); + public static T ThrowOperationCanceledException(string? message, CancellationToken token) + => throw new OperationCanceledException(message, token); /// /// Throws a new . @@ -668,7 +701,9 @@ public static partial class ThrowHelper /// Thrown with the specified parameters. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowOperationCanceledException(string? message, Exception? innerException, CancellationToken token) => throw new OperationCanceledException(message, innerException, token); + public static T ThrowOperationCanceledException(string? message, Exception? innerException, + CancellationToken token) + => throw new OperationCanceledException(message, innerException, token); /// /// Throws a new . @@ -687,7 +722,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowPlatformNotSupportedException(string? message) => throw new PlatformNotSupportedException(message); + public static T ThrowPlatformNotSupportedException(string? message) + => throw new PlatformNotSupportedException(message); /// /// Throws a new . @@ -698,7 +734,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowPlatformNotSupportedException(string? message, Exception? innerException) => throw new PlatformNotSupportedException(message, innerException); + public static T ThrowPlatformNotSupportedException(string? message, Exception? innerException) + => throw new PlatformNotSupportedException(message, innerException); /// /// Throws a new . @@ -717,7 +754,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowSynchronizationLockException(string? message) => throw new SynchronizationLockException(message); + public static T ThrowSynchronizationLockException(string? message) + => throw new SynchronizationLockException(message); /// /// Throws a new . @@ -728,7 +766,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowSynchronizationLockException(string? message, Exception? innerException) => throw new SynchronizationLockException(message, innerException); + public static T ThrowSynchronizationLockException(string? message, Exception? innerException) + => throw new SynchronizationLockException(message, innerException); /// /// Throws a new . @@ -758,7 +797,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowTimeoutException(string? message, Exception? innerException) => throw new TimeoutException(message, innerException); + public static T ThrowTimeoutException(string? message, Exception? innerException) + => throw new TimeoutException(message, innerException); /// /// Throws a new . @@ -777,7 +817,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowUnauthorizedAccessException(string? message) => throw new UnauthorizedAccessException(message); + public static T ThrowUnauthorizedAccessException(string? message) + => throw new UnauthorizedAccessException(message); /// /// Throws a new . @@ -788,7 +829,8 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowUnauthorizedAccessException(string? message, Exception? innerException) => throw new UnauthorizedAccessException(message, innerException); + public static T ThrowUnauthorizedAccessException(string? message, Exception? innerException) + => throw new UnauthorizedAccessException(message, innerException); /// /// Throws a new . @@ -839,5 +881,6 @@ public static partial class ThrowHelper /// Thrown with the specified parameter. /// This method always throws, so it actually never returns a value. [DoesNotReturn] - public static T ThrowWin32Exception(string? message, Exception? innerException) => throw new Win32Exception(message, innerException); -} + public static T ThrowWin32Exception(string? message, Exception? innerException) + => throw new Win32Exception(message, innerException); +} \ No newline at end of file diff --git a/Source/FisheryLib/Diagnostics/ThrowHelper.cs b/Source/FisheryLib/Diagnostics/ThrowHelper.cs index 5a7f795..ea27bb2 100644 --- a/Source/FisheryLib/Diagnostics/ThrowHelper.cs +++ b/Source/FisheryLib/Diagnostics/ThrowHelper.cs @@ -28,7 +28,8 @@ public static partial class ThrowHelper /// The message to include in the exception. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowArrayTypeMismatchException(string? message) => throw new ArrayTypeMismatchException(message); + public static void ThrowArrayTypeMismatchException(string? message) + => throw new ArrayTypeMismatchException(message); /// /// Throws a new . @@ -37,7 +38,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowArrayTypeMismatchException(string? message, Exception? innerException) => throw new ArrayTypeMismatchException(message, innerException); + public static void ThrowArrayTypeMismatchException(string? message, Exception? innerException) + => throw new ArrayTypeMismatchException(message, innerException); /// /// Throws a new . @@ -61,7 +63,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowArgumentException(string? message, Exception? innerException) => throw new ArgumentException(message, innerException); + public static void ThrowArgumentException(string? message, Exception? innerException) + => throw new ArgumentException(message, innerException); /// /// Throws a new . @@ -70,7 +73,8 @@ public static partial class ThrowHelper /// The message to include in the exception. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowArgumentException(string? name, string? message) => throw new ArgumentException(message, name); + public static void ThrowArgumentException(string? name, string? message) + => throw new ArgumentException(message, name); /// /// Throws a new . @@ -80,7 +84,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowArgumentException(string? name, string? message, Exception? innerException) => throw new ArgumentException(message, name, innerException); + public static void ThrowArgumentException(string? name, string? message, Exception? innerException) + => throw new ArgumentException(message, name, innerException); /// /// Throws a new . @@ -104,7 +109,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowArgumentNullException(string? name, Exception? innerException) => throw new ArgumentNullException(name, innerException); + public static void ThrowArgumentNullException(string? name, Exception? innerException) + => throw new ArgumentNullException(name, innerException); /// /// Throws a new . @@ -113,7 +119,8 @@ public static partial class ThrowHelper /// The message to include in the exception. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowArgumentNullException(string? name, string? message) => throw new ArgumentNullException(name, message); + public static void ThrowArgumentNullException(string? name, string? message) + => throw new ArgumentNullException(name, message); /// /// Throws a new . @@ -137,7 +144,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowArgumentOutOfRangeException(string? name, Exception? innerException) => throw new ArgumentOutOfRangeException(name, innerException); + public static void ThrowArgumentOutOfRangeException(string? name, Exception? innerException) + => throw new ArgumentOutOfRangeException(name, innerException); /// /// Throws a new . @@ -146,7 +154,8 @@ public static partial class ThrowHelper /// The message to include in the exception. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowArgumentOutOfRangeException(string? name, string? message) => throw new ArgumentOutOfRangeException(name, message); + public static void ThrowArgumentOutOfRangeException(string? name, string? message) + => throw new ArgumentOutOfRangeException(name, message); /// /// Throws a new . @@ -156,7 +165,8 @@ public static partial class ThrowHelper /// The message to include in the exception. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowArgumentOutOfRangeException(string? name, object? value, string? message) => throw new ArgumentOutOfRangeException(name, value, message); + public static void ThrowArgumentOutOfRangeException(string? name, object? value, string? message) + => throw new ArgumentOutOfRangeException(name, value, message); /// /// Throws a new . @@ -180,7 +190,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowCOMException(string? message, Exception? innerException) => throw new COMException(message, innerException); + public static void ThrowCOMException(string? message, Exception? innerException) + => throw new COMException(message, innerException); /// /// Throws a new . @@ -213,7 +224,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowExternalException(string? message, Exception? innerException) => throw new ExternalException(message, innerException); + public static void ThrowExternalException(string? message, Exception? innerException) + => throw new ExternalException(message, innerException); /// /// Throws a new . @@ -222,7 +234,8 @@ public static partial class ThrowHelper /// The HRESULT of the error to include. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowExternalException(string? message, int error) => throw new ExternalException(message, error); + public static void ThrowExternalException(string? message, int error) + => throw new ExternalException(message, error); /// /// Throws a new . @@ -246,7 +259,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowFormatException(string? message, Exception? innerException) => throw new FormatException(message, innerException); + public static void ThrowFormatException(string? message, Exception? innerException) + => throw new FormatException(message, innerException); /// /// Throws a new . @@ -261,7 +275,8 @@ public static partial class ThrowHelper /// The message to include in the exception. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowInsufficientMemoryException(string? message) => throw new InsufficientMemoryException(message); + public static void ThrowInsufficientMemoryException(string? message) + => throw new InsufficientMemoryException(message); /// /// Throws a new . @@ -270,7 +285,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowInsufficientMemoryException(string? message, Exception? innerException) => throw new InsufficientMemoryException(message, innerException); + public static void ThrowInsufficientMemoryException(string? message, Exception? innerException) + => throw new InsufficientMemoryException(message, innerException); /// /// Throws a new . @@ -294,7 +310,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowInvalidDataException(string? message, Exception? innerException) => throw new InvalidDataException(message, innerException); + public static void ThrowInvalidDataException(string? message, Exception? innerException) + => throw new InvalidDataException(message, innerException); /// /// Throws a new . @@ -318,7 +335,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowInvalidOperationException(string? message, Exception? innerException) => throw new InvalidOperationException(message, innerException); + public static void ThrowInvalidOperationException(string? message, Exception? innerException) + => throw new InvalidOperationException(message, innerException); /// /// Throws a new . @@ -342,7 +360,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowLockRecursionException(string? message, Exception? innerException) => throw new LockRecursionException(message, innerException); + public static void ThrowLockRecursionException(string? message, Exception? innerException) + => throw new LockRecursionException(message, innerException); /// /// Throws a new . @@ -366,7 +385,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowMissingFieldException(string? message, Exception? innerException) => throw new MissingFieldException(message, innerException); + public static void ThrowMissingFieldException(string? message, Exception? innerException) + => throw new MissingFieldException(message, innerException); /// /// Throws a new . @@ -375,7 +395,8 @@ public static partial class ThrowHelper /// The target field being retrieved. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowMissingFieldException(string? className, string? fieldName) => throw new MissingFieldException(className, fieldName); + public static void ThrowMissingFieldException(string? className, string? fieldName) + => throw new MissingFieldException(className, fieldName); /// /// Throws a new . @@ -399,7 +420,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowMissingMemberException(string? message, Exception? innerException) => throw new MissingMemberException(message, innerException); + public static void ThrowMissingMemberException(string? message, Exception? innerException) + => throw new MissingMemberException(message, innerException); /// /// Throws a new . @@ -408,7 +430,8 @@ public static partial class ThrowHelper /// The target member being retrieved. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowMissingMemberException(string? className, string? memberName) => throw new MissingMemberException(className, memberName); + public static void ThrowMissingMemberException(string? className, string? memberName) + => throw new MissingMemberException(className, memberName); /// /// Throws a new . @@ -432,7 +455,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowMissingMethodException(string? message, Exception? innerException) => throw new MissingMethodException(message, innerException); + public static void ThrowMissingMethodException(string? message, Exception? innerException) + => throw new MissingMethodException(message, innerException); /// /// Throws a new . @@ -441,7 +465,8 @@ public static partial class ThrowHelper /// The target method being retrieved. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowMissingMethodException(string? className, string? methodName) => throw new MissingMethodException(className, methodName); + public static void ThrowMissingMethodException(string? className, string? methodName) + => throw new MissingMethodException(className, methodName); /// /// Throws a new . @@ -465,7 +490,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowNotSupportedException(string? message, Exception? innerException) => throw new NotSupportedException(message, innerException); + public static void ThrowNotSupportedException(string? message, Exception? innerException) + => throw new NotSupportedException(message, innerException); /// /// Throws a new . @@ -473,7 +499,8 @@ public static partial class ThrowHelper /// The name of the disposed object. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowObjectDisposedException(string? objectName) => throw new ObjectDisposedException(objectName); + public static void ThrowObjectDisposedException(string? objectName) + => throw new ObjectDisposedException(objectName); /// /// Throws a new . @@ -482,7 +509,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowObjectDisposedException(string? objectName, Exception? innerException) => throw new ObjectDisposedException(objectName, innerException); + public static void ThrowObjectDisposedException(string? objectName, Exception? innerException) + => throw new ObjectDisposedException(objectName, innerException); /// /// Throws a new . @@ -491,7 +519,8 @@ public static partial class ThrowHelper /// The message to include in the exception. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowObjectDisposedException(string? objectName, string? message) => throw new ObjectDisposedException(objectName, message); + public static void ThrowObjectDisposedException(string? objectName, string? message) + => throw new ObjectDisposedException(objectName, message); /// /// Throws a new . @@ -506,7 +535,8 @@ public static partial class ThrowHelper /// The message to include in the exception. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowOperationCanceledException(string? message) => throw new OperationCanceledException(message); + public static void ThrowOperationCanceledException(string? message) + => throw new OperationCanceledException(message); /// /// Throws a new . @@ -515,7 +545,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowOperationCanceledException(string? message, Exception? innerException) => throw new OperationCanceledException(message, innerException); + public static void ThrowOperationCanceledException(string? message, Exception? innerException) + => throw new OperationCanceledException(message, innerException); /// /// Throws a new . @@ -523,7 +554,8 @@ public static partial class ThrowHelper /// The in use. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowOperationCanceledException(CancellationToken token) => throw new OperationCanceledException(token); + public static void ThrowOperationCanceledException(CancellationToken token) + => throw new OperationCanceledException(token); /// /// Throws a new . @@ -532,7 +564,8 @@ public static partial class ThrowHelper /// The in use. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowOperationCanceledException(string? message, CancellationToken token) => throw new OperationCanceledException(message, token); + public static void ThrowOperationCanceledException(string? message, CancellationToken token) + => throw new OperationCanceledException(message, token); /// /// Throws a new . @@ -542,7 +575,9 @@ public static partial class ThrowHelper /// The in use. /// Thrown with the specified parameters. [DoesNotReturn] - public static void ThrowOperationCanceledException(string? message, Exception? innerException, CancellationToken token) => throw new OperationCanceledException(message, innerException, token); + public static void ThrowOperationCanceledException(string? message, Exception? innerException, + CancellationToken token) + => throw new OperationCanceledException(message, innerException, token); /// /// Throws a new . @@ -557,7 +592,8 @@ public static partial class ThrowHelper /// The message to include in the exception. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowPlatformNotSupportedException(string? message) => throw new PlatformNotSupportedException(message); + public static void ThrowPlatformNotSupportedException(string? message) + => throw new PlatformNotSupportedException(message); /// /// Throws a new . @@ -566,7 +602,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowPlatformNotSupportedException(string? message, Exception? innerException) => throw new PlatformNotSupportedException(message, innerException); + public static void ThrowPlatformNotSupportedException(string? message, Exception? innerException) + => throw new PlatformNotSupportedException(message, innerException); /// /// Throws a new . @@ -581,7 +618,8 @@ public static partial class ThrowHelper /// The message to include in the exception. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowSynchronizationLockException(string? message) => throw new SynchronizationLockException(message); + public static void ThrowSynchronizationLockException(string? message) + => throw new SynchronizationLockException(message); /// /// Throws a new . @@ -590,7 +628,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowSynchronizationLockException(string? message, Exception? innerException) => throw new SynchronizationLockException(message, innerException); + public static void ThrowSynchronizationLockException(string? message, Exception? innerException) + => throw new SynchronizationLockException(message, innerException); /// /// Throws a new . @@ -614,7 +653,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowTimeoutException(string? message, Exception? innerException) => throw new TimeoutException(message, innerException); + public static void ThrowTimeoutException(string? message, Exception? innerException) + => throw new TimeoutException(message, innerException); /// /// Throws a new . @@ -629,7 +669,8 @@ public static partial class ThrowHelper /// The message to include in the exception. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowUnauthorizedAccessException(string? message) => throw new UnauthorizedAccessException(message); + public static void ThrowUnauthorizedAccessException(string? message) + => throw new UnauthorizedAccessException(message); /// /// Throws a new . @@ -638,7 +679,8 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowUnauthorizedAccessException(string? message, Exception? innerException) => throw new UnauthorizedAccessException(message, innerException); + public static void ThrowUnauthorizedAccessException(string? message, Exception? innerException) + => throw new UnauthorizedAccessException(message, innerException); /// /// Throws a new . @@ -679,5 +721,6 @@ public static partial class ThrowHelper /// The inner to include. /// Thrown with the specified parameter. [DoesNotReturn] - public static void ThrowWin32Exception(string? message, Exception? innerException) => throw new Win32Exception(message, innerException); -} + public static void ThrowWin32Exception(string? message, Exception? innerException) + => throw new Win32Exception(message, innerException); +} \ No newline at end of file diff --git a/Source/FisheryLib/Equality.cs b/Source/FisheryLib/Equality.cs index fe90726..53d1183 100644 --- a/Source/FisheryLib/Equality.cs +++ b/Source/FisheryLib/Equality.cs @@ -12,16 +12,17 @@ namespace FisheryLib; public static class Equality { /// - /// Generic overload of Equals to avoid boxing. Makes use of function pointers for best performance and does not throw on null for reference types. + /// Generic overload of Equals to avoid boxing. Makes use of function pointers for best performance and does not + /// throw on null for reference types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool Equals([AllowNull] this T @this, [AllowNull] T other) => FunctionPointers.Equals.Default(@this!, other!); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe bool Equals([AllowNull] this ref T @this, [AllowNull] ref T other) + public static unsafe bool Equals(this ref T @this, ref T other) where T : struct // required by the compiler for 'this ref' - => FunctionPointers.EqualsByRef.Default(ref @this!, ref other!); + => FunctionPointers.EqualsByRef.Default(ref @this, ref other); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe bool EqualsByRef([AllowNull] ref T @this, [AllowNull] ref T other) diff --git a/Source/FisheryLib/FishPatch.cs b/Source/FisheryLib/FishPatch.cs index c33ba8b..42ac7a5 100644 --- a/Source/FisheryLib/FishPatch.cs +++ b/Source/FisheryLib/FishPatch.cs @@ -335,4 +335,5 @@ public abstract class ClassWithFishPatches : IHasFishPatch public virtual FishPatchHolder? PatchHolder { get => _patchHolder; set => _patchHolder = value; } public virtual bool RequiresLoadedGameForPatching => false; private FishPatchHolder? _patchHolder; -}*/ \ No newline at end of file +}*/ + diff --git a/Source/FisheryLib/FishTranspiler.cs b/Source/FisheryLib/FishTranspiler.cs index bb8c282..d8f875a 100644 --- a/Source/FisheryLib/FishTranspiler.cs +++ b/Source/FisheryLib/FishTranspiler.cs @@ -7,11 +7,18 @@ using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection.Emit; +using System.Security; +using JetBrains.Annotations; +using Verse; +// ReSharper disable PossibleMultipleEnumeration namespace FisheryLib; + +[PublicAPI] public static class FishTranspiler { - public struct Container + [PublicAPI] + public readonly struct Container : IEquatable, IEquatable { public Container(OpCode opcode, object? operand = null) { @@ -34,31 +41,37 @@ public static class FishTranspiler } public override string ToString() - => $"{OpCode} {Operand}"; + => $"{OpCode} {Operand switch + { + MemberInfo memberInfo => memberInfo.FullDescription(), + null => "NULL", + _ => Operand.ToString() + }}"; - public CodeInstruction ToInstruction() - => new(OpCode, Operand); + [SecuritySafeCritical] + public CodeInstruction ToInstruction() => new(OpCode, Operand); public Container Store() - => OpCode.LoadsArgument() ? ToStoreArgument() - : new( - OpCode.LoadsField() || OpCode.StoresField() ? OpCode.ToStoreField() - : OpCode.LoadsLocalVariable() || OpCode.StoresLocalVariable() ? OpCode.ToStoreLocalVariable() - : OpCode.StoresArgument() ? OpCode - : ThrowHelper.ThrowInvalidOperationException($"Cannot determine Store instruction for {OpCode}"), - Operand); + => OpCode.LoadsArgument() + ? ToStoreArgument() + : new(OpCode.LoadsField() || OpCode.StoresField() ? OpCode.ToStoreField() + : OpCode.LoadsLocalVariable() || OpCode.StoresLocalVariable() ? OpCode.ToStoreLocalVariable() + : OpCode.StoresArgument() ? OpCode + : ThrowHelper.ThrowInvalidOperationException( + $"Cannot determine Store instruction for {OpCode}"), + Operand); public Container Load() - => OpCode.StoresArgument() ? ToLoadArgument() - : new( - OpCode.LoadsField() || OpCode.StoresField() ? OpCode.ToLoadField() - : OpCode.LoadsLocalVariable() || OpCode.StoresLocalVariable() ? OpCode.ToLoadLocalVariable() - : OpCode.LoadsArgument() ? OpCode - : ThrowHelper.ThrowInvalidOperationException($"Cannot determine Load instruction for {OpCode}"), - Operand); + => OpCode.StoresArgument() + ? ToLoadArgument() + : new(OpCode.LoadsField() || OpCode.StoresField() ? OpCode.ToLoadField() + : OpCode.LoadsLocalVariable() || OpCode.StoresLocalVariable() ? OpCode.ToLoadLocalVariable() + : OpCode.LoadsArgument() ? OpCode + : ThrowHelper.ThrowInvalidOperationException( + $"Cannot determine Load instruction for {OpCode}"), + Operand); - private Container ToStoreArgument() - => new(OpCode.ToStoreArgument(), GetIndex()); + private Container ToStoreArgument() => new(OpCode.ToStoreArgument(), GetIndex()); private Container ToLoadArgument() { @@ -66,12 +79,11 @@ public static class FishTranspiler return new(GetLoadArgumentOpCode(index), index > 3 ? index : null); } - public Container Address() - => new(OpCode.ToAddress(), GetIndex()); + public Container Address() => new(OpCode.ToAddress(), GetIndex()); - public CodeInstruction WithLabels(params Label[] labels) - => WithLabels((IEnumerable public int Pops => PopsNone() ? 0 - : PopsOne() ? 1 - : PopsTwo() ? 2 - : PopsThree() ? 3 - : OpCode.Calls() ? GetCallPops() - : OpCode.Branches() ? GetBranchPops() - : throw new NotImplementedException(); + : PopsOne() ? 1 + : PopsTwo() ? 2 + : PopsThree() ? 3 + : OpCode.Calls() ? GetCallPops() + : OpCode.Branches() ? GetBranchPops() + : ThrowHelper.ThrowNotSupportedException($"OpCode { + OpCode} not supported by FishTranspiler.Pops"); /// /// Gets the number of values pushed onto the stack by this instruction /// public int Pushes => PushesNone() ? 0 - : PushesOne() ? 1 - : PushesTwo() ? 2 - : OpCode.Calls() ? GetCallPushes() - : throw new NotImplementedException(); + : PushesOne() ? 1 + : PushesTwo() ? 2 + : OpCode.Calls() ? GetCallPushes() + : ThrowHelper.ThrowNotSupportedException($"OpCode { + OpCode} not supported by FishTranspiler.Pushes"); private bool PopsNone() => OpCode == OpCodes.Nop - || OpCode == OpCodes.Jmp - || OpCode == OpCodes.Ldftn - || OpCode.LoadsArgument() - || OpCode.LoadsConstant() - || OpCode.LoadsStaticField() - || OpCode.LoadsLocalVariable(); + || OpCode == OpCodes.Jmp + || OpCode == OpCodes.Ldftn + || OpCode.LoadsArgument() + || OpCode.LoadsConstant() + || OpCode.LoadsStaticField() + || OpCode.LoadsLocalVariable(); private bool PopsOne() => OpCode == OpCodes.Pop - || OpCode == OpCodes.Initobj - || OpCode == OpCodes.Dup - || OpCode == OpCodes.Isinst - || OpCode == OpCodes.Not - || OpCode == OpCodes.Neg - || OpCode == OpCodes.Ldlen - || OpCode == OpCodes.Newarr - || OpCode == OpCodes.Ldvirtftn - || OpCode.StoresArgument() - || OpCode.StoresStaticField() - || OpCode.LoadsInstanceField() - || OpCode.StoresLocalVariable() - || OpCode.LoadsByRef() - || OpCode.Converts(); + || OpCode == OpCodes.Initobj + || OpCode == OpCodes.Dup + || OpCode == OpCodes.Isinst + || OpCode == OpCodes.Not + || OpCode == OpCodes.Neg + || OpCode == OpCodes.Ldlen + || OpCode == OpCodes.Newarr + || OpCode == OpCodes.Ldvirtftn + || OpCode.StoresArgument() + || OpCode.StoresStaticField() + || OpCode.LoadsInstanceField() + || OpCode.StoresLocalVariable() + || OpCode.LoadsByRef() + || OpCode.Converts(); private bool PopsTwo() => OpCode == OpCodes.Cpobj - || OpCode.StoresInstanceField() - || OpCode.LoadsElement() - || OpCode.StoresByRef() - || OpCode.Compares() - || OpCode.Computes(); + || OpCode.StoresInstanceField() + || OpCode.LoadsElement() + || OpCode.StoresByRef() + || OpCode.Compares() + || OpCode.Computes(); private bool PopsThree() => OpCode == OpCodes.Cpblk - || OpCode == OpCodes.Initblk - || OpCode.StoresElement(); + || OpCode == OpCodes.Initblk + || OpCode.StoresElement(); private int GetCallPops() => OpCode == OpCodes.Calli - || Operand is not MethodInfo methodInfo ? ThrowHelper.ThrowNotSupportedException() - : methodInfo.GetParameters().Length + (methodInfo.IsStatic ? 0 : 1); + || Operand is not MethodInfo methodInfo + ? ThrowHelper.ThrowNotSupportedException() + : methodInfo.GetParameters().Length + (methodInfo.IsStatic ? 0 : 1); private int GetBranchPops() => OpCode == OpCodes.Brtrue - || OpCode == OpCodes.Brtrue_S - || OpCode == OpCodes.Brfalse - || OpCode == OpCodes.Brfalse_S - || OpCode == OpCodes.Switch ? 1 - : OpCode == OpCodes.Br - || OpCode == OpCodes.Br_S ? 0 - : 2; // greater, less, equal, etc + || OpCode == OpCodes.Brtrue_S + || OpCode == OpCodes.Brfalse + || OpCode == OpCodes.Brfalse_S + || OpCode == OpCodes.Switch ? 1 + : OpCode == OpCodes.Br + || OpCode == OpCodes.Br_S ? 0 + : 2; // greater, less, equal, etc private bool PushesNone() => OpCode == OpCodes.Nop - || OpCode == OpCodes.Jmp - || OpCode == OpCodes.Pop - || OpCode == OpCodes.Cpblk - || OpCode == OpCodes.Initblk - || OpCode == OpCodes.Cpobj - || OpCode == OpCodes.Initobj - || OpCode.StoresArgument() - || OpCode.StoresField() - || OpCode.StoresLocalVariable() - || OpCode.StoresElement() - || OpCode.StoresByRef() - || OpCode.Branches(); + || OpCode == OpCodes.Jmp + || OpCode == OpCodes.Pop + || OpCode == OpCodes.Cpblk + || OpCode == OpCodes.Initblk + || OpCode == OpCodes.Cpobj + || OpCode == OpCodes.Initobj + || OpCode.StoresArgument() + || OpCode.StoresField() + || OpCode.StoresLocalVariable() + || OpCode.StoresElement() + || OpCode.StoresByRef() + || OpCode.Branches(); private bool PushesOne() => OpCode == OpCodes.Isinst - || OpCode == OpCodes.Ldlen - || OpCode == OpCodes.Newarr - || OpCode == OpCodes.Newobj - || OpCode == OpCodes.Ldftn - || OpCode == OpCodes.Ldvirtftn - || OpCode.LoadsArgument() - || OpCode.LoadsConstant() - || OpCode.LoadsField() - || OpCode.LoadsLocalVariable() - || OpCode.LoadsElement() - || OpCode.LoadsByRef() - || OpCode.Compares() - || OpCode.Computes() - || OpCode.Converts(); + || OpCode == OpCodes.Ldlen + || OpCode == OpCodes.Newarr + || OpCode == OpCodes.Newobj + || OpCode == OpCodes.Ldftn + || OpCode == OpCodes.Ldvirtftn + || OpCode.LoadsArgument() + || OpCode.LoadsConstant() + || OpCode.LoadsField() + || OpCode.LoadsLocalVariable() + || OpCode.LoadsElement() + || OpCode.LoadsByRef() + || OpCode.Compares() + || OpCode.Computes() + || OpCode.Converts(); - private bool PushesTwo() - => OpCode == OpCodes.Dup; + private bool PushesTwo() => OpCode == OpCodes.Dup; private int GetCallPushes() => OpCode == OpCodes.Calli - || Operand is not MethodInfo methodInfo ? ThrowHelper.ThrowNotSupportedException() - : methodInfo.ReturnType == typeof(void) ? 0 - : 1; + || Operand is not MethodInfo methodInfo ? ThrowHelper.ThrowNotSupportedException() + : methodInfo.ReturnType == typeof(void) ? 0 + : 1; } /// @@ -481,8 +500,11 @@ public static class FishTranspiler Guard.IsNotNull(predicate); var locals = MatchingLocalVariables(method, predicate); - return locals.Any() ? locals.First() - : ThrowHelper.ThrowInvalidOperationException($"No local variable found for predicate {predicate.Method.FullDescription()} and method {method.FullDescription()}"); + return locals.Any() + ? locals.First() + : ThrowHelper.ThrowInvalidOperationException( + $"No local variable found for predicate {predicate.Method.FullDescription()} and method { + method.FullDescription()}"); } /// @@ -490,7 +512,8 @@ public static class FishTranspiler /// /// The MethodBase of the method to search for local variables in /// The predicate used to identify the local variables - public static IEnumerable MatchingLocalVariables(MethodBase method, Predicate predicate) + public static IEnumerable MatchingLocalVariables(MethodBase method, + Predicate predicate) { Guard.IsNotNull(method); Guard.IsNotNull(predicate); @@ -511,8 +534,10 @@ public static class FishTranspiler Guard.IsNotNull(localType); var operands = GetLocalOperandsOrIndices(codes, c => c.Returns(localType)); - return operands.Any() ? LocalVariable(operands.First()) - : ThrowHelper.ThrowInvalidOperationException($"No local variable found with localType {localType.FullDescription()}"); + return operands.Any() + ? LocalVariable(operands.First()) + : ThrowHelper.ThrowInvalidOperationException( + $"No local variable found with localType {localType.FullDescription()}"); } /// @@ -570,8 +595,10 @@ public static class FishTranspiler Guard.IsNotNull(predicate); var locals = StoreMatchingLocalVariables(method, predicate); - return locals.Any() ? locals.First() - : ThrowHelper.ThrowInvalidOperationException($"No local variable found for predicate {predicate.Method.FullDescription()} and method {method.FullDescription()}"); + return locals.Any() + ? locals.First() + : ThrowHelper.ThrowInvalidOperationException( + $"No local variable found for predicate {predicate.Method.FullDescription()} and method {method.FullDescription()}"); } /// @@ -580,7 +607,8 @@ public static class FishTranspiler /// /// The MethodBase of the method to search for local variables in /// The predicate used to identify the local variables - public static IEnumerable StoreMatchingLocalVariables(MethodBase method, Predicate predicate) + public static IEnumerable StoreMatchingLocalVariables(MethodBase method, + Predicate predicate) { Guard.IsNotNull(method); Guard.IsNotNull(predicate); @@ -601,8 +629,10 @@ public static class FishTranspiler Guard.IsNotNull(localType); var operands = GetLocalOperandsOrIndices(codes, c => c.Returns(localType)); - return operands.Any() ? StoreLocalVariable(operands.First()) - : ThrowHelper.ThrowInvalidOperationException($"No local variable found with localType {localType.FullDescription()}"); + return operands.Any() + ? StoreLocalVariable(operands.First()) + : ThrowHelper.ThrowInvalidOperationException( + $"No local variable found with localType {localType.FullDescription()}"); } /// @@ -634,23 +664,39 @@ public static class FishTranspiler /// the local variable list at index. /// /// The index of the local variable - public static Container StoreLocalVariable(int index) => new(GetStoreLocalOpCode(index), GetOperandFromIndex(index)); + public static Container StoreLocalVariable(int index) + => new(GetStoreLocalOpCode(index), GetOperandFromIndex(index)); /// - /// Declares a local variable of the specified type, optionally pinning the object referred - /// to by the variable. Then returns an instruction matching that of StoreLocalVariable. + /// Declares a local variable of the specified type. Then returns an instruction matching that of StoreLocalVariable. /// /// A System.Type object that represents the type of the local variable /// The ILGenerator used for the instruction stream - /// True to pin the object in memory; otherwise, false - public static Container NewLocalVariable(Type localType, ILGenerator generator, bool pinned = false) + public static Container NewLocalVariable(Type localType, ILGenerator generator) { Guard.IsNotNull(localType); Guard.IsNotNull(generator); - return StoreLocalVariable(generator.DeclareLocal(localType, pinned)); + return NewLocalVariable(localType, generator, false); } + /// + /// Declares a local variable of the specified type, pinning the object referred to by the variable, + /// for use with unsafe pointers. Then returns an instruction matching that of StoreLocalVariable. + /// + /// A System.Type object that represents the type of the local variable + /// The ILGenerator used for the instruction stream + public static Container NewFixedLocalVariable(Type localType, ILGenerator generator) + { + Guard.IsNotNull(localType); + Guard.IsNotNull(generator); + + return NewLocalVariable(localType, generator, true); + } + + private static Container NewLocalVariable(Type localType, ILGenerator generator, bool pinned) + => StoreLocalVariable(generator.DeclareLocal(localType, pinned)); + /// /// Pushes a supplied value of type int32 onto the evaluation stack as an int32. /// @@ -683,13 +729,13 @@ public static class FishTranspiler => e.GetTypeCode() switch { TypeCode.SByte - or TypeCode.Byte - or TypeCode.Int16 - or TypeCode.UInt16 - or TypeCode.Int32 - or TypeCode.UInt32 => Constant(System.Convert.ToInt32(e)), + or TypeCode.Byte + or TypeCode.Int16 + or TypeCode.UInt16 + or TypeCode.Int32 + or TypeCode.UInt32 => Constant(System.Convert.ToInt32(e)), TypeCode.Int64 - or TypeCode.UInt64 => Constant(System.Convert.ToInt64(e)), + or TypeCode.UInt64 => Constant(System.Convert.ToInt64(e)), var typeCode => InvalidEnumForConstant(e, typeCode) }; @@ -708,14 +754,15 @@ public static class FishTranspiler public static Container Constant(MemberInfo memberInfo) => Token(memberInfo); /// - /// Pushes a supplied value onto the evaluation stack as an int32, int64, float, double, null reference, RuntimeHandle or a reference to a string literal stored in the metadata. + /// Pushes a supplied value onto the evaluation stack as an int32, int64, float, double, null reference, + /// RuntimeHandle or a reference to a string literal stored in the metadata. /// - /// The value to push onto the evaluation stack - public static Container Constant(object obj) - => obj switch + /// The value to push onto the evaluation stack + public static Container Constant(object value) + => value switch { int intValue => Constant(intValue), - long or ulong => Constant(System.Convert.ToInt64(obj)), + long or ulong => Constant(System.Convert.ToInt64(value)), float floatValue => Constant(floatValue), double doubleValue => Constant(doubleValue), Enum enumValue => Constant(enumValue), @@ -723,12 +770,15 @@ public static class FishTranspiler MemberInfo memberInfo => Token(memberInfo), null => Null, IConvertible convertible => Constant(convertible.ToInt32(CultureInfo.InvariantCulture)), - _ => ThrowHelper.ThrowArgumentException(nameof(obj), $"Type {obj.GetType()} cannot be used for FishTranspiler.Constant") + _ => ThrowHelper.ThrowArgumentException(nameof(value), + $"Type {value.GetType()} cannot be used for FishTranspiler.Constant") }; private static Container InvalidEnumForConstant(Enum e, TypeCode typeCode) => ThrowHelper.ThrowArgumentException( - $"Tried using Enum {e.GetType().FullDescription()} with underlying Type {Enum.GetUnderlyingType(e.GetType()).FullDescription()} (TypeCode: {typeCode}) for FishTranspiler.Constant."); + $"Tried using Enum {e.GetType().FullDescription()} with underlying Type { + Enum.GetUnderlyingType(e.GetType()).FullDescription()} (TypeCode: { + typeCode}) for FishTranspiler.Constant."); /// /// Calls the method indicated by the passed method group. @@ -748,6 +798,7 @@ public static class FishTranspiler /// /// An expression with a single method call /// force a Call OpCode instead of using Callvirt where appropriate + [SecuritySafeCritical] public static Container Call(Expression expression, bool forceNoCallvirt = false) { Guard.IsNotNull(expression); @@ -762,15 +813,21 @@ public static class FishTranspiler /// The name of the type the method is declared in /// The name of the method /// An optional array of parameter types to specify the desired method overload - /// An optional array of generic parameter types to specify the method's generic parameters + /// + /// An optional array of generic parameter types to specify the method's generic parameters + /// /// force a Call OpCode instead of using Callvirt where appropriate - public static Container Call(string assembly, string type, string name, Type[]? parameters = null, Type[]? generics = null, bool forceNoCallvirt = false) + public static Container Call(string assembly, string type, string name, Type[]? parameters = null, + Type[]? generics = null, bool forceNoCallvirt = false) { Guard.IsNotNullOrEmpty(assembly); Guard.IsNotNullOrEmpty(type); Guard.IsNotNullOrEmpty(name); - return Call(Type.GetType($"{type}, {assembly}"), name, parameters, generics, forceNoCallvirt); + return Call( + Type.GetType($"{type}, {assembly}") + ?? ThrowHelper.ThrowArgumentException($"No type named {type} found in assembly {assembly}"), name, + parameters, generics, forceNoCallvirt); } /// @@ -779,14 +836,23 @@ public static class FishTranspiler /// The type the method is declared in /// The name of the method /// An optional array of parameter types to specify the desired method overload - /// An optional array of generic parameter types to specify the method's generic parameters + /// + /// An optional array of generic parameter types to specify the method's generic parameters + /// /// force a Call OpCode instead of using Callvirt where appropriate - public static Container Call(Type type, string name, Type[]? parameters = null, Type[]? generics = null, bool forceNoCallvirt = false) + [SecuritySafeCritical] + public static Container Call(Type type, string name, Type[]? parameters = null, Type[]? generics = null, + bool forceNoCallvirt = false) { Guard.IsNotNull(type); Guard.IsNotNullOrEmpty(name); - return Call(AccessTools.Method(type, name, parameters, generics), forceNoCallvirt); + return Call( + AccessTools.Method(type, name, parameters, generics) + ?? ThrowHelper.ThrowArgumentException($"No method found with type {type}, name { + name}{(parameters != null ? $", parameters: {parameters.ToStringSafeEnumerable()}" : "")}{ + (generics != null ? $", generics: {generics.ToStringSafeEnumerable()}" : "")}"), + forceNoCallvirt); } /// @@ -799,7 +865,10 @@ public static class FishTranspiler { Guard.IsNotNull(method); - return new(method.IsStatic || forceNoCallvirt ? OpCodes.Call : OpCodes.Callvirt, method); + return new( + method.IsStatic || (method.DeclaringType?.IsValueType ?? false) || forceNoCallvirt + ? OpCodes.Call + : OpCodes.Callvirt, method); } /// @@ -810,6 +879,7 @@ public static class FishTranspiler /// The type the field is declared in /// The name of the field /// No field found for the specified type and name + [SecuritySafeCritical] public static Container Field(Type type, string name) { Guard.IsNotNull(type); @@ -818,7 +888,8 @@ public static class FishTranspiler var field = AccessTools.Field(type, name); return field != null ? new(field.IsStatic ? OpCodes.Ldsfld : OpCodes.Ldfld, field) - : ThrowHelper.ThrowMissingFieldException($"FishTranspiler.LoadField failed to find a field at {type.FullDescription()}:{name}"); + : ThrowHelper.ThrowMissingFieldException( + $"FishTranspiler.LoadField failed to find a field at {type.FullDescription()}:{name}"); } /// @@ -842,6 +913,7 @@ public static class FishTranspiler /// The type the field is declared in /// The name of the field /// No field found for the specified type and name + [SecuritySafeCritical] public static Container FieldAddress(Type type, string name) { Guard.IsNotNull(type); @@ -850,7 +922,8 @@ public static class FishTranspiler var field = AccessTools.Field(type, name); return field != null ? new(field.IsStatic ? OpCodes.Ldsflda : OpCodes.Ldflda, field) - : ThrowHelper.ThrowMissingFieldException($"FishTranspiler.LoadFieldAddress failed to find a field at {type.FullDescription()}:{name}"); + : ThrowHelper.ThrowMissingFieldException( + $"FishTranspiler.LoadFieldAddress failed to find a field at {type.FullDescription()}:{name}"); } /// @@ -874,6 +947,7 @@ public static class FishTranspiler /// The type the field is declared in /// The name of the field /// No field found for the specified type and name + [SecuritySafeCritical] public static Container StoreField(Type type, string name) { Guard.IsNotNull(type); @@ -882,7 +956,8 @@ public static class FishTranspiler var field = AccessTools.Field(type, name); return field != null ? new(field.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld, field) - : ThrowHelper.ThrowMissingFieldException($"FishTranspiler.StoreField failed to find a field at {type.FullDescription()}:{name}"); + : ThrowHelper.ThrowMissingFieldException( + $"FishTranspiler.StoreField failed to find a field at {type.FullDescription()}:{name}"); } /// @@ -921,6 +996,7 @@ public static class FishTranspiler /// The name of the property /// force a Call OpCode instead of using Callvirt where appropriate /// No property found for the specified type and name + [SecuritySafeCritical] public static Container PropertyGetter(Type type, string name, bool forceNoCallvirt = false) { Guard.IsNotNull(type); @@ -929,7 +1005,9 @@ public static class FishTranspiler var method = AccessTools.PropertyGetter(type, name); return method != null ? Call(method, forceNoCallvirt) - : ThrowHelper.ThrowMissingMethodException($"FishTranspiler.CallPropertyGetter failed to find a property getter at {type.FullDescription()}:{name}"); + : ThrowHelper.ThrowMissingMethodException( + $"FishTranspiler.CallPropertyGetter failed to find a property getter at { + type.FullDescription()}:{name}"); } /// @@ -945,7 +1023,9 @@ public static class FishTranspiler var method = propertyInfo.GetMethod; return method != null ? Call(method, forceNoCallvirt) - : ThrowHelper.ThrowMissingMethodException($"FishTranspiler.CallPropertyGetter failed to find a property getter at {propertyInfo.DeclaringType.FullDescription()}:{propertyInfo.Name}"); + : ThrowHelper.ThrowMissingMethodException( + $"FishTranspiler.CallPropertyGetter failed to find a property getter at { + propertyInfo.DeclaringType.FullDescription()}:{propertyInfo.Name}"); } /// @@ -955,6 +1035,7 @@ public static class FishTranspiler /// The name of the property /// force a Call OpCode instead of using Callvirt where appropriate /// No property found with specified type and name + [SecuritySafeCritical] public static Container PropertySetter(Type type, string name, bool forceNoCallvirt = false) { Guard.IsNotNull(type); @@ -963,7 +1044,9 @@ public static class FishTranspiler var method = AccessTools.PropertySetter(type, name); return method != null ? Call(method, forceNoCallvirt) - : ThrowHelper.ThrowMissingMethodException($"FishTranspiler.CallPropertySetter failed to find a property setter at {type.FullDescription()}:{name}"); + : ThrowHelper.ThrowMissingMethodException( + $"FishTranspiler.CallPropertySetter failed to find a property setter at { + type.FullDescription()}:{name}"); } /// @@ -979,13 +1062,15 @@ public static class FishTranspiler var method = propertyInfo.SetMethod; return method != null ? Call(method, forceNoCallvirt) - : ThrowHelper.ThrowMissingMethodException($"FishTranspiler.CallPropertySetter failed to find a property setter at {propertyInfo.DeclaringType.FullDescription()}:{propertyInfo.Name}"); + : ThrowHelper.ThrowMissingMethodException( + $"FishTranspiler.CallPropertySetter failed to find a property setter at { + propertyInfo.DeclaringType.FullDescription()}:{propertyInfo.Name}"); } /// /// Constrains the type on which a virtual method call is made. Normally used for generics. /// - /// The type to constrait to + /// The type to constrain to public static Container Constrained() => Constrained(typeof(T)); /// @@ -1013,15 +1098,18 @@ public static class FishTranspiler /// /// The type of the new object /// An optional array of parameter types to specify the desired constructor overload - /// Whether to search for static or instance constructors public static Container New(Type type, Type[]? parameters = null) { Guard.IsNotNull(type); return typeof(Array).IsAssignableFrom(type) ? new(OpCodes.Newarr, type.GetElementType()) - : (parameters is null || parameters.Length == 0) && type.IsValueType ? new(OpCodes.Initobj, type) - : New(AccessTools.Constructor(type, parameters)); + : type.IsValueType + && (parameters is null || parameters.Length == 0) + && !Array.Exists(type.GetConstructors(AccessTools.allDeclared & ~BindingFlags.Static), + static c => c.GetParameters().Length == 0) + ? new(OpCodes.Initobj, type) + : New(Reflection.MatchingConstructor(type, parameters, throwOnFailure: true)!); } /// @@ -1104,6 +1192,18 @@ public static class FishTranspiler /// The label attached to the target instruction public static Container IfNotNull(Label label) => new(OpCodes.Brtrue, label); + /// + /// Unconditionally transfers control to a target instruction. + /// + /// The label attached to the target instruction + public static Container GoTo(Label label) => new(OpCodes.Br, label); + + /// + /// Unconditionally transfers control to a target instruction. Short form for short jumps. + /// + /// The label attached to the target instruction + public static Container GoTo_Short(Label label) => new(OpCodes.Br_S, label); + /// /// Transfers control to a target instruction if two values are equal. /// Short form for short jumps. @@ -1117,6 +1217,19 @@ public static class FishTranspiler /// The label attached to the target instruction public static Container IfEqual(Label label) => new(OpCodes.Beq, label); + /// + /// Transfers control to a target instruction if two values are not equal or unordered. + /// Short form for short jumps. + /// + /// The label attached to the target instruction + public static Container IfNotEqualOrUnordered_Short(Label label) => new(OpCodes.Bne_Un_S, label); + + /// + /// Transfers control to a target instruction if two values are not equal or unordered. + /// + /// The label attached to the target instruction + public static Container IfNotEqualOrUnordered(Label label) => new(OpCodes.Bne_Un, label); + /// /// Transfers control to a target instruction if the first value is /// greater than the second value. Short form for short jumps. @@ -1131,6 +1244,20 @@ public static class FishTranspiler /// The label attached to the target instruction public static Container IfGreaterThan(Label label) => new(OpCodes.Bgt, label); + /// + /// Transfers control to a target instruction if the first value is + /// greater than the second value or if any value is unordered. Short form for short jumps. + /// + /// The label attached to the target instruction + public static Container IfGreaterThanOrUnordered_Short(Label label) => new(OpCodes.Bgt_Un_S, label); + + /// + /// Transfers control to a target instruction if the first value is + /// greater than the second value or if any value is unordered. + /// + /// The label attached to the target instruction + public static Container IfGreaterThanOrUnordered(Label label) => new(OpCodes.Bgt_Un, label); + /// /// Transfers control to a target instruction if the first value is /// greater than or equal to the second value. Short form for short jumps. @@ -1145,6 +1272,21 @@ public static class FishTranspiler /// The label attached to the target instruction public static Container IfGreaterThanOrEqual(Label label) => new(OpCodes.Bge, label); + /// + /// Transfers control to a target instruction if the first value is + /// greater than or equal to the second value or if any value is unordered. + /// Short form for short jumps. + /// + /// The label attached to the target instruction + public static Container IfGreaterThanOrEqualOrUnordered_Short(Label label) => new(OpCodes.Bge_Un_S, label); + + /// + /// Transfers control to a target instruction if the first value is + /// greater than or equal to the second value or if any value is unordered. + /// + /// The label attached to the target instruction + public static Container IfGreaterThanOrEqualOrUnordered(Label label) => new(OpCodes.Bge_Un, label); + /// /// Transfers control to a target instruction if the first value is /// less than the second value. Short form for short jumps. @@ -1159,6 +1301,21 @@ public static class FishTranspiler /// The label attached to the target instruction public static Container IfLessThan(Label label) => new(OpCodes.Blt, label); + /// + /// Transfers control to a target instruction if the first value is + /// less than the second value or if any value is unordered. + /// Short form for short jumps. + /// + /// The label attached to the target instruction + public static Container IfLessThanOrUnordered_Short(Label label) => new(OpCodes.Blt_Un_S, label); + + /// + /// Transfers control to a target instruction if the first value is + /// less than the second value or if any value is unordered. + /// + /// The label attached to the target instruction + public static Container IfLessThanOrUnordered(Label label) => new(OpCodes.Blt_Un, label); + /// /// Transfers control to a target instruction if the first value is /// less than or equal to the second value. Short form for short jumps. @@ -1173,6 +1330,32 @@ public static class FishTranspiler /// The label attached to the target instruction public static Container IfLessThanOrEqual(Label label) => new(OpCodes.Ble, label); + /// + /// Transfers control to a target instruction if the first value is + /// less than or equal to the second value or if any value is unordered. + /// Short form for short jumps. + /// + /// The label attached to the target instruction + public static Container IfLessThanOrEqualOrUnordered_Short(Label label) => new(OpCodes.Ble_Un_S, label); + + /// + /// Transfers control to a target instruction if the first value is + /// less than or equal to the second value or if any value is unordered. + /// + /// The label attached to the target instruction + public static Container IfLessThanOrEqualOrUnordered(Label label) => new(OpCodes.Ble_Un, label); + + /// + /// Implements a jump table. + /// + public static Container Switch(params Label[] labels) + { + Guard.IsNotNull(labels); + Guard.HasSizeGreaterThan(labels, 0); + + return new(OpCodes.Switch, labels); + } + /// /// Loads a value of the specified type onto the evaluation stack indirectly or copies /// the value type object pointed to by an address to the top of the evaluation stack. @@ -1185,6 +1368,7 @@ public static class FishTranspiler /// the value type object pointed to by an address to the top of the evaluation stack. /// /// The object's type + [SecuritySafeCritical] public static Container LoadByRef(Type? type) { if (type is null) @@ -1194,7 +1378,7 @@ public static class FishTranspiler type = type.GetElementType(); return AccessTools.IsStruct(type) - ? LoadObject(type) + ? LoadObject(type!) : LoadIndirectly(type); } @@ -1212,6 +1396,7 @@ public static class FishTranspiler /// memory address. /// /// The object's type + [SecuritySafeCritical] public static Container StoreByRef(Type? type) { if (type is null) @@ -1221,7 +1406,7 @@ public static class FishTranspiler type = type.GetElementType(); return AccessTools.IsStruct(type) - ? StoreObject(type) + ? StoreObject(type!) : StoreIndirectly(type); } @@ -1245,7 +1430,10 @@ public static class FishTranspiler return type.IsValueType ? new(OpCodes.Ldobj, type) - : ThrowHelper.ThrowArgumentException($"Type ({type}) for FishTranspiler.LoadObject must be a ValueType. Use LoadByRef or LoadIndirectly for primitive and reference types.", nameof(type)); + : ThrowHelper.ThrowArgumentException( + $"Type ({type}) for FishTranspiler.LoadObject must be a ValueType. Use LoadByRef or " + + "LoadIndirectly for primitive and reference types.", + nameof(type)); } /// @@ -1268,19 +1456,22 @@ public static class FishTranspiler return type.IsValueType ? new(OpCodes.Stobj, type) - : ThrowHelper.ThrowArgumentException($"Type ({type}) for FishTranspiler.StoreObject must be a ValueType. Use StoreByRef or StoreIndirectly for primitive and reference types.", nameof(type)); + : ThrowHelper.ThrowArgumentException( + $"Type ({type}) for FishTranspiler.StoreObject must be a ValueType. Use StoreByRef or " + + "StoreIndirectly for primitive and reference types.", + nameof(type)); } /// /// Converts a value type to an object reference (type O). /// - /// The object's type + /// The type of the value public static Container Box() => new(OpCodes.Box, typeof(T)); /// /// Converts a value type to an object reference (type O). /// - /// The object's type + /// The type of the value /// type is null /// type is not a ValueType public static Container Box(Type type) @@ -1289,50 +1480,90 @@ public static class FishTranspiler return type.IsValueType ? new(OpCodes.Box, type) - : ThrowHelper.ThrowArgumentException($"Type ({type}) for FishTranspiler.Box must be a ValueType.", nameof(type)); + : ThrowHelper.ThrowArgumentException( + $"Type ({type}) for FishTranspiler.Box must be a ValueType.", nameof(type)); } + /// - /// Converts the boxed representation of a value type to its unboxed form. + /// Converts the boxed representation of a value type to its unboxed form, pushing an address to the box object's + /// held value to the stack. Implemented using the Unbox instruction. /// /// The object's type - public static Container Unbox() => Unbox(typeof(T)); + public static Container UnboxAddress() => UnboxAddress(typeof(T)); /// - /// Converts the boxed representation of a value type to its unboxed form. + /// Converts the boxed representation of a value type to its unboxed form, pushing an address to the box object's + /// held value to the stack. Implemented using the Unbox instruction. /// /// The object's type /// type is null /// type is not a ValueType - public static Container Unbox(Type type) + public static Container UnboxAddress(Type type) { Guard.IsNotNull(type); return type.IsValueType ? new(OpCodes.Unbox, type) - : ThrowHelper.ThrowArgumentException($"Type ({type}) for FishTranspiler.Unbox must be a ValueType.", nameof(type)); + : ThrowHelper.ThrowArgumentException( + $"Type ({type}) for FishTranspiler.UnboxAddress must be a ValueType.", nameof(type)); } /// - /// Converts the boxed representation of a type specified in the instruction to its - /// unboxed form. + /// Converts the boxed representation of a value type to its unboxed form, pushing an address to the box object's + /// held value to the stack. Implemented using the Unbox instruction. /// /// The object's type - public static Container UnboxAny() => UnboxAny(typeof(T)); + [Obsolete("Renamed to UnboxAddress for clarity")] + public static Container Unbox() => UnboxAddress(); /// - /// Converts the boxed representation of a type specified in the instruction to its - /// unboxed form. + /// Converts the boxed representation of a value type to its unboxed form, pushing an address to the box object's + /// held value to the stack. Implemented using the Unbox instruction. /// /// The object's type /// type is null - public static Container UnboxAny(Type type) + /// type is not a ValueType + [Obsolete("Renamed to UnboxAddress for clarity")] + public static Container Unbox(Type type) => UnboxAddress(type); + + /// + /// Converts the boxed representation of a type specified in the instruction to its unboxed form, pushing the value + /// to the stack. Implemented using the UnboxAny instruction. + /// + /// The object's type + public static Container UnboxValue() => UnboxValue(typeof(T)); + + /// + /// Converts the boxed representation of a type specified in the instruction to its unboxed form, pushing the value + /// to the stack. Implemented using the UnboxAny instruction. + /// + /// The object's type + /// type is null + public static Container UnboxValue(Type type) { Guard.IsNotNull(type); return new(OpCodes.Unbox_Any, type); } + /// + /// Converts the boxed representation of a type specified in the instruction to its unboxed form, pushing the value + /// to the stack. Implemented using the UnboxAny instruction. + /// + /// The object's type + [Obsolete("Renamed to UnboxValue for clarity")] + public static Container UnboxAny() => UnboxValue(); + + /// + /// Converts the boxed representation of a type specified in the instruction to its unboxed form, pushing the value + /// to the stack. Implemented using the UnboxAny instruction. + /// + /// The object's type + /// type is null + [Obsolete("Renamed to UnboxValue for clarity")] + public static Container UnboxAny(Type type) => UnboxValue(type); + /// /// Loads the current instance onto the evaluation stack. /// @@ -1386,12 +1617,14 @@ public static class FishTranspiler public static Container MultiplyChecked => new(OpCodes.Mul_Ovf); /// - /// Multiplies two unsigned integer values, performs an overflow check, and pushes the result onto the evaluation stack. + /// Multiplies two unsigned integer values, performs an overflow check, and pushes the result onto the evaluation + /// stack. /// public static Container MultiplyCheckedUnsigned => new(OpCodes.Mul_Ovf_Un); /// - /// Divides two values and pushes the result as a floating-point (type F) or quotient (type int32) onto the evaluation stack. + /// Divides two values and pushes the result as a floating-point (type F) or quotient (type int32) onto the + /// evaluation stack. /// public static Container Divide => new(OpCodes.Div); @@ -1406,28 +1639,89 @@ public static class FishTranspiler public static Container Subtract => new(OpCodes.Sub); /// - /// Subtracts one integer value from another, performs an overflow check, and pushes the result onto the evaluation stack. + /// Subtracts one integer value from another, performs an overflow check, and pushes the result onto the evaluation + /// stack. /// public static Container SubtractChecked => new(OpCodes.Sub_Ovf); /// - /// Subtracts one unsigned integer value from another, performs an overflow check, and pushes the result onto the evaluation stack. + /// Subtracts one unsigned integer value from another, performs an overflow check, and pushes the result onto the + /// evaluation stack. /// public static Container SubtractCheckedUnsigned => new(OpCodes.Sub_Ovf_Un); + /// + /// Divides two values and pushes the remainder onto the evaluation stack. + /// + public static Container Remainder => new(OpCodes.Rem); + + /// + /// Divides two unsigned values and pushes the remainder onto the evaluation stack. + /// + public static Container RemainderUnsigned => new(OpCodes.Rem_Un); + + /// + /// Computes the bitwise AND of two values and pushes the result onto the evaluation + /// stack. + /// + public static Container And => new(OpCodes.And); + + /// + /// Compute the bitwise complement of the two integer values on top of the stack + /// and pushes the result onto the evaluation stack. + /// + public static Container Or => new(OpCodes.Or); + + /// + /// Computes the bitwise XOR of the top two values on the evaluation stack, pushing + /// the result onto the evaluation stack. + /// + public static Container Xor => new(OpCodes.Xor); + + /// + /// Shifts an integer value to the left (in zeroes) by a specified number of bits, + /// pushing the result onto the evaluation stack. + /// + public static Container LeftShift => new(OpCodes.Shl); + + /// + /// Shifts an integer value (in sign) to the right by a specified number of bits, + /// pushing the result onto the evaluation stack. + /// + public static Container RightShift => new(OpCodes.Shr); + + /// + /// Shifts an unsigned integer value (in zeroes) to the right by a specified number + /// of bits, pushing the result onto the evaluation stack. + /// + public static Container RightShiftUnsigned => new(OpCodes.Shr_Un); + + /// + /// Negates a value and pushes the result onto the evaluation stack. + /// + public static Container Negate => new(OpCodes.Neg); + + /// + /// Computes the bitwise complement of the integer value on top of the stack and + /// pushes the result onto the evaluation stack as the same type. + /// + public static Container Not => new(OpCodes.Not); + /// /// Copies a specified number bytes from a source address to a destination address. /// public static Container CopyBlock => new(OpCodes.Cpblk); /// - /// Copies the value type located at the address of an object (type &, or native int) to the address of the destination object (type &, or native int). + /// Copies the value type located at the address of an object (type &, or native int) to the address of the + /// destination object (type &, or native int). /// /// The type of the value public static Container CopyObject() => CopyObject(typeof(T)); /// - /// Copies the value type located at the address of an object (type &, or native int) to the address of the destination object (type &, or native int). + /// Copies the value type located at the address of an object (type &, or native int) to the address of the + /// destination object (type &, or native int). /// /// The type of the value public static Container CopyObject(Type type) @@ -1449,18 +1743,26 @@ public static class FishTranspiler /// The type of the value public static Container LoadIndirectly(Type? type) => new(type is null ? OpCodes.Ldind_Ref - : type.IsPointer ? OpCodes.Ldind_I - : type.IsEnum ? OpCodes.Ldind_I4 - : type == typeof(sbyte) || type == typeof(bool) ? OpCodes.Ldind_I1 - : type == typeof(short) ? OpCodes.Ldind_I2 - : type == typeof(int) ? OpCodes.Ldind_I4 - : type == typeof(long) || type == typeof(ulong) ? OpCodes.Ldind_I8 - : type == typeof(float) ? OpCodes.Ldind_R4 - : type == typeof(double) ? OpCodes.Ldind_R8 - : type == typeof(byte) ? OpCodes.Ldind_U1 - : type == typeof(ushort) || type == typeof(char) ? OpCodes.Ldind_U2 - : type == typeof(uint) ? OpCodes.Ldind_U4 - : OpCodes.Ldind_Ref); + : type.IsPointer ? OpCodes.Ldind_I + : type.IsEnum ? OpCodes.Ldind_I4 + : _loadIndirectlyOpCodesByType.TryGetValue(type, out var value) ? value + : OpCodes.Ldind_Ref); + + private static readonly Dictionary _loadIndirectlyOpCodesByType = new() + { + { typeof(sbyte), OpCodes.Ldind_I1 }, + { typeof(bool), OpCodes.Ldind_I1 }, + { typeof(short), OpCodes.Ldind_I2 }, + { typeof(int), OpCodes.Ldind_I4 }, + { typeof(long), OpCodes.Ldind_I8 }, + { typeof(ulong), OpCodes.Ldind_I8 }, + { typeof(float), OpCodes.Ldind_R4 }, + { typeof(double), OpCodes.Ldind_R8 }, + { typeof(byte), OpCodes.Ldind_U1 }, + { typeof(ushort), OpCodes.Ldind_U2 }, + { typeof(char), OpCodes.Ldind_U2 }, + { typeof(uint), OpCodes.Ldind_U4 } + }; /// /// Stores a value of the specified type at a supplied address. @@ -1474,15 +1776,26 @@ public static class FishTranspiler /// The type of the value public static Container StoreIndirectly(Type? type) => new(type is null ? OpCodes.Stind_Ref - : type.IsPointer ? OpCodes.Stind_I - : type.IsEnum ? OpCodes.Stind_I4 - : type == typeof(sbyte) || type == typeof(byte) || type == typeof(bool) ? OpCodes.Stind_I1 - : type == typeof(short) || type == typeof(ushort) || type == typeof(char) ? OpCodes.Stind_I2 - : type == typeof(int) || type == typeof(uint) ? OpCodes.Stind_I4 - : type == typeof(long) || type == typeof(ulong) ? OpCodes.Stind_I8 - : type == typeof(float) ? OpCodes.Stind_R4 - : type == typeof(double) ? OpCodes.Stind_R8 - : OpCodes.Stind_Ref); + : type.IsPointer ? OpCodes.Stind_I + : type.IsEnum ? OpCodes.Stind_I4 + : _storeIndirectlyOpCodesByType.TryGetValue(type, out var value) ? value + : OpCodes.Stind_Ref); + + private static readonly Dictionary _storeIndirectlyOpCodesByType = new() + { + { typeof(sbyte), OpCodes.Stind_I1 }, + { typeof(byte), OpCodes.Stind_I1 }, + { typeof(bool), OpCodes.Stind_I1 }, + { typeof(short), OpCodes.Stind_I2 }, + { typeof(ushort), OpCodes.Stind_I2 }, + { typeof(char), OpCodes.Stind_I2 }, + { typeof(int), OpCodes.Stind_I4 }, + { typeof(uint), OpCodes.Stind_I4 }, + { typeof(long), OpCodes.Stind_I8 }, + { typeof(ulong), OpCodes.Stind_I8 }, + { typeof(float), OpCodes.Stind_R4 }, + { typeof(double), OpCodes.Stind_R8 } + }; /// /// Replaces the array element at a given index with the value on the evaluation @@ -1498,15 +1811,24 @@ public static class FishTranspiler /// The element's type public static Container StoreElement(Type? type) => type is null ? new(OpCodes.Stelem_Ref) - : type.IsPointer ? new(OpCodes.Stelem_I) - : !type.IsValueType ? new(OpCodes.Stelem_Ref) - : type == typeof(sbyte) || type == typeof(byte) ? new(OpCodes.Stelem_I1) - : type == typeof(short) || type == typeof(ushort) ? new(OpCodes.Stelem_I2) - : type == typeof(int) || type == typeof(uint) ? new(OpCodes.Stelem_I4) - : type == typeof(long) || type == typeof(ulong) ? new(OpCodes.Stelem_I8) - : type == typeof(float) ? new(OpCodes.Stelem_R4) - : type == typeof(double) ? new(OpCodes.Stelem_R8) - : new(OpCodes.Stelem, type); + : type.IsPointer ? new(OpCodes.Stelem_I) + : !type.IsValueType ? new(OpCodes.Stelem_Ref) + : _storeElementOpCodesByType.TryGetValue(type, out var value) ? new(value) + : new(OpCodes.Stelem, type); + + private static readonly Dictionary _storeElementOpCodesByType = new() + { + { typeof(sbyte), OpCodes.Stelem_I1 }, + { typeof(byte), OpCodes.Stelem_I1 }, + { typeof(short), OpCodes.Stelem_I2 }, + { typeof(ushort), OpCodes.Stelem_I2 }, + { typeof(int), OpCodes.Stelem_I4 }, + { typeof(uint), OpCodes.Stelem_I4 }, + { typeof(long), OpCodes.Stelem_I8 }, + { typeof(ulong), OpCodes.Stelem_I8 }, + { typeof(float), OpCodes.Stelem_R4 }, + { typeof(double), OpCodes.Stelem_R8 } + }; /// /// Loads the element at a specified array index onto the top of the evaluation stack @@ -1520,20 +1842,26 @@ public static class FishTranspiler /// as the type specified in the instruction. /// /// The element's type - public static Container LoadElement(Type type) + public static Container LoadElement(Type? type) => type is null ? new(OpCodes.Ldelem_Ref) - : type.IsPointer ? new(OpCodes.Ldelem_I) - : !type.IsValueType ? new(OpCodes.Ldelem_Ref) - : type == typeof(sbyte) ? new(OpCodes.Ldelem_I1) - : type == typeof(short) ? new(OpCodes.Ldelem_I2) - : type == typeof(int) ? new(OpCodes.Ldelem_I4) - : type == typeof(long) || type == typeof(ulong) ? new(OpCodes.Ldelem_I8) - : type == typeof(float) ? new(OpCodes.Ldelem_R4) - : type == typeof(double) ? new(OpCodes.Ldelem_R8) - : type == typeof(byte) ? new(OpCodes.Ldelem_U1) - : type == typeof(ushort) ? new(OpCodes.Ldelem_U2) - : type == typeof(uint) ? new(OpCodes.Ldelem_U4) - : new(OpCodes.Ldelem, type); + : type.IsPointer ? new(OpCodes.Ldelem_I) + : !type.IsValueType ? new(OpCodes.Ldelem_Ref) + : _loadElementOpCodesByType.TryGetValue(type, out var value) ? new(value) + : new(OpCodes.Ldelem, type); + + private static readonly Dictionary _loadElementOpCodesByType = new() + { + { typeof(sbyte), OpCodes.Ldelem_I1 }, + { typeof(short), OpCodes.Ldelem_I2 }, + { typeof(int), OpCodes.Ldelem_I4 }, + { typeof(long), OpCodes.Ldelem_I8 }, + { typeof(ulong), OpCodes.Ldelem_I8 }, + { typeof(float), OpCodes.Ldelem_R4 }, + { typeof(double), OpCodes.Ldelem_R8 }, + { typeof(byte), OpCodes.Ldelem_U1 }, + { typeof(ushort), OpCodes.Ldelem_U2 }, + { typeof(uint), OpCodes.Ldelem_U4 } + }; /// /// Loads the address of the array element at a specified array index onto the top @@ -1554,6 +1882,12 @@ public static class FishTranspiler return new(OpCodes.Ldelema, type); } + /// + /// Pushes the number of elements of a zero-based, one-dimensional array onto the + /// evaluation stack. + /// + public static Container Length => new(OpCodes.Ldlen); + /// /// Converts a metadata token to its runtime representation, pushing it onto the /// evaluation stack. @@ -1642,28 +1976,34 @@ public static class FishTranspiler { Guard.IsNotNull(type, nameof(type)); - return new( - !type.IsPrimitive || type == typeof(char) ? ThrowHelper.ThrowArgumentException("Convert expects a non-character primitive type", nameof(type)) - : type == typeof(byte) ? OpCodes.Conv_U1 - : type == typeof(sbyte) || type == typeof(bool) ? OpCodes.Conv_I1 - : type == typeof(short) ? OpCodes.Conv_I2 - : type == typeof(ushort) ? OpCodes.Conv_U2 - : type == typeof(int) ? OpCodes.Conv_I4 - : type == typeof(uint) ? OpCodes.Conv_U4 - : type == typeof(long) ? OpCodes.Conv_I8 - : type == typeof(ulong) ? OpCodes.Conv_U8 - : type == typeof(IntPtr) ? OpCodes.Conv_I - : type == typeof(UIntPtr) ? OpCodes.Conv_U - : type == typeof(float) ? OpCodes.Conv_R4 - : type == typeof(double) ? OpCodes.Conv_R8 - : ThrowHelper.ThrowArgumentException($"Type {type.FullDescription()} cannot be converted to")); + return new(!type.IsPrimitive || type == typeof(char) + ? ThrowHelper.ThrowArgumentException("Convert expects a non-character primitive type", nameof(type)) + : ConvertOpCodesByType.TryGetValue(type, out var value) ? value + : ThrowHelper.ThrowArgumentException( + $"Type {type.FullDescription()} cannot be converted to")); } + public static readonly Dictionary ConvertOpCodesByType = new() + { + { typeof(byte), OpCodes.Conv_U1 }, + { typeof(sbyte), OpCodes.Conv_I1 }, + { typeof(bool), OpCodes.Conv_I1 }, + { typeof(short), OpCodes.Conv_I2 }, + { typeof(ushort), OpCodes.Conv_U2 }, + { typeof(int), OpCodes.Conv_I4 }, + { typeof(uint), OpCodes.Conv_U4 }, + { typeof(long), OpCodes.Conv_I8 }, + { typeof(ulong), OpCodes.Conv_U8 }, + { typeof(IntPtr), OpCodes.Conv_I }, + { typeof(UIntPtr), OpCodes.Conv_U }, + { typeof(float), OpCodes.Conv_R4 }, + { typeof(double), OpCodes.Conv_R8 } + }; + /// /// Converts the primitive value on top of the evaluation stack to float32 as if it were unsigned. /// - public static Container ConvertUnsignedToFloat() - => new(OpCodes.Conv_R_Un); + public static Container ConvertUnsignedToFloat() => new(OpCodes.Conv_R_Un); /// /// Converts the signed value on top of the evaluation stack to the given non-character primitive @@ -1683,23 +2023,35 @@ public static class FishTranspiler { Guard.IsNotNull(type); - return new( - !type.IsPrimitive || type == typeof(char) ? ThrowHelper.ThrowArgumentException("ConvertWithOverflowCheck expects a non-character primitive type") - : type == typeof(float) ? ThrowHelper.ThrowInvalidOperationException("There is no operation for converting to a float with overflow checking") - : type == typeof(double) ? ThrowHelper.ThrowInvalidOperationException("There is no operation for converting to a double with overflow checking") - : type == typeof(byte) ? OpCodes.Conv_Ovf_U1 - : type == typeof(sbyte) || type == typeof(bool) ? OpCodes.Conv_Ovf_I1 - : type == typeof(short) ? OpCodes.Conv_Ovf_I2 - : type == typeof(ushort) ? OpCodes.Conv_Ovf_U2 - : type == typeof(int) ? OpCodes.Conv_Ovf_I4 - : type == typeof(uint) ? OpCodes.Conv_Ovf_U4 - : type == typeof(long) ? OpCodes.Conv_Ovf_I8 - : type == typeof(ulong) ? OpCodes.Conv_Ovf_U8 - : type == typeof(IntPtr) ? OpCodes.Conv_Ovf_I - : type == typeof(UIntPtr) ? OpCodes.Conv_Ovf_U - : ThrowHelper.ThrowArgumentException($"Type {type.FullDescription()} cannot be converted to")); + return new(!type.IsPrimitive || type == typeof(char) + ? ThrowHelper.ThrowArgumentException( + "ConvertWithOverflowCheck expects a non-character primitive type") + : type == typeof(float) + ? ThrowHelper.ThrowInvalidOperationException( + "There is no operation for converting to a float with overflow checking") + : type == typeof(double) + ? ThrowHelper.ThrowInvalidOperationException( + "There is no operation for converting to a double with overflow checking") + : ConvertWithOverflowCheckOpCodesByType.TryGetValue(type, out var value) ? value + : ThrowHelper.ThrowArgumentException( + $"Type {type.FullDescription()} cannot be converted to")); } + public static readonly Dictionary ConvertWithOverflowCheckOpCodesByType = new() + { + { typeof(byte), OpCodes.Conv_Ovf_U1 }, + { typeof(sbyte), OpCodes.Conv_Ovf_I1 }, + { typeof(bool), OpCodes.Conv_Ovf_I1 }, + { typeof(short), OpCodes.Conv_Ovf_I2 }, + { typeof(ushort), OpCodes.Conv_Ovf_U2 }, + { typeof(int), OpCodes.Conv_Ovf_I4 }, + { typeof(uint), OpCodes.Conv_Ovf_U4 }, + { typeof(long), OpCodes.Conv_Ovf_I8 }, + { typeof(ulong), OpCodes.Conv_Ovf_U8 }, + { typeof(IntPtr), OpCodes.Conv_Ovf_I }, + { typeof(UIntPtr), OpCodes.Conv_Ovf_U } + }; + /// /// Converts the value on top of the evaluation stack to the given non-character primitive type /// as if it were unsigned, throwing System.OverflowException on overflow. @@ -1718,33 +2070,52 @@ public static class FishTranspiler { Guard.IsNotNull(type, nameof(type)); - return new( - !type.IsPrimitive || type == typeof(char) ? ThrowHelper.ThrowArgumentException("ConvertUnsignedWithOverflowCheck expects a non-character primitive type") - : type == typeof(float) ? ThrowHelper.ThrowInvalidOperationException("There is no operation for converting to a float with overflow checking") - : type == typeof(double) ? ThrowHelper.ThrowInvalidOperationException("There is no operation for converting to a double with overflow checking") - : type == typeof(byte) ? OpCodes.Conv_Ovf_U1_Un - : type == typeof(sbyte) ? OpCodes.Conv_Ovf_I1_Un - : type == typeof(short) ? OpCodes.Conv_Ovf_I2_Un - : type == typeof(ushort) ? OpCodes.Conv_Ovf_U2_Un - : type == typeof(int) ? OpCodes.Conv_Ovf_I4_Un - : type == typeof(uint) ? OpCodes.Conv_Ovf_U4_Un - : type == typeof(long) ? OpCodes.Conv_Ovf_I8_Un - : type == typeof(ulong) ? OpCodes.Conv_Ovf_U8_Un - : type == typeof(IntPtr) ? OpCodes.Conv_Ovf_I_Un - : type == typeof(UIntPtr) ? OpCodes.Conv_Ovf_U_Un - : ThrowHelper.ThrowArgumentException($"Type {type.FullDescription()} cannot be converted to")); + return new(!type.IsPrimitive || type == typeof(char) + ? ThrowHelper.ThrowArgumentException( + "ConvertUnsignedWithOverflowCheck expects a non-character primitive type") + : type == typeof(float) + ? ThrowHelper.ThrowInvalidOperationException( + "There is no operation for converting to a float with overflow checking") + : type == typeof(double) + ? ThrowHelper.ThrowInvalidOperationException( + "There is no operation for converting to a double with overflow checking") + : ConvertUnsignedWithOverflowCheckOpCodesByType.TryGetValue(type, out var value) ? value + : ThrowHelper.ThrowArgumentException( + $"Type {type.FullDescription()} cannot be converted to")); } - public static bool CallReturns(this CodeInstruction instruction, Type type) => (instruction.opcode == OpCodes.Callvirt || instruction.opcode == OpCodes.Call) && ((MethodInfo)instruction.operand).ReturnType == type; - public static bool FieldReturns(this CodeInstruction instruction, Type type) => (instruction.opcode == OpCodes.Ldfld || instruction.opcode == OpCodes.Ldsfld) && ((FieldInfo)instruction.operand).FieldType == type; - public static bool Returns(this CodeInstruction instruction, Type type) => instruction.CallReturns(type) || instruction.FieldReturns(type); + public static readonly Dictionary ConvertUnsignedWithOverflowCheckOpCodesByType = new() + { + { typeof(byte), OpCodes.Conv_Ovf_U1_Un }, + { typeof(sbyte), OpCodes.Conv_Ovf_I1_Un }, + { typeof(short), OpCodes.Conv_Ovf_I2_Un }, + { typeof(ushort), OpCodes.Conv_Ovf_U2_Un }, + { typeof(int), OpCodes.Conv_Ovf_I4_Un }, + { typeof(uint), OpCodes.Conv_Ovf_U4_Un }, + { typeof(long), OpCodes.Conv_Ovf_I8_Un }, + { typeof(ulong), OpCodes.Conv_Ovf_U8_Un }, + { typeof(IntPtr), OpCodes.Conv_Ovf_I_Un }, + { typeof(UIntPtr), OpCodes.Conv_Ovf_U_Un } + }; + + public static bool CallReturns(this CodeInstruction instruction, Type type) + => (instruction.opcode == OpCodes.Callvirt || instruction.opcode == OpCodes.Call) + && ((MethodInfo)instruction.operand).ReturnType == type; + + public static bool FieldReturns(this CodeInstruction instruction, Type type) + => (instruction.opcode == OpCodes.Ldfld || instruction.opcode == OpCodes.Ldsfld) + && ((FieldInfo)instruction.operand).FieldType == type; + + public static bool Returns(this CodeInstruction instruction, Type type) + => instruction.CallReturns(type) || instruction.FieldReturns(type); public static int FirstArgumentIndex(MethodBase method, Type argumentType) { Guard.IsNotNull(method); Guard.IsNotNull(argumentType); - return !method.IsStatic && method.DeclaringType == argumentType ? 0 + return !method.IsStatic && method.DeclaringType == argumentType + ? 0 : FirstArgumentIndex(method, p => p.ParameterType == argumentType); } @@ -1755,8 +2126,10 @@ public static class FishTranspiler var argument = method.GetParameters().FirstOrDefault(predicate); - return argument != null ? ArgumentIndex(argument) - : ThrowHelper.ThrowInvalidOperationException($"No argument found for predicate {predicate.Method.FullDescription()} and method {method.FullDescription()}"); + return argument != null + ? ArgumentIndex(argument) + : ThrowHelper.ThrowInvalidOperationException( + $"No argument found for predicate {predicate.Method.FullDescription()} and method {method.FullDescription()}"); } public static int ArgumentIndex(ParameterInfo parameter) @@ -1766,7 +2139,8 @@ public static class FishTranspiler return parameter.Position + (((MethodBase)parameter.Member).IsStatic ? 0 : 1); } - public static IEnumerable GetLocalOperandsOrIndices(CodeInstructions codes, Predicate predicate) + public static IEnumerable GetLocalOperandsOrIndices(CodeInstructions codes, + Predicate predicate) { Guard.IsNotNull(codes); Guard.IsNotNull(predicate); @@ -1778,8 +2152,10 @@ public static class FishTranspiler { yield return opcode.TryGetIndex() ?? TryGetIndexFromOperand(code.operand) - ?? ThrowHelper.ThrowNotSupportedException($"{code.opcode} returned {code.operand?.ToString() ?? "null"}."); + ?? ThrowHelper.ThrowNotSupportedException( + $"{code.opcode} returned {code.operand?.ToString() ?? "null"}."); } + previousCode = code; } } @@ -1798,7 +2174,11 @@ public static class FishTranspiler Guard.IsNotNull(method); Guard.IsNotNull(predicate); - var variables = method.GetMethodBody().LocalVariables; + var methodBody = method.GetMethodBody(); + if (methodBody is null) + ThrowHelper.ThrowArgumentException($"Method {method.FullDescription()} has no body"); + + var variables = methodBody.LocalVariables; for (var i = 0; i < variables.Count; i++) { if (predicate(variables[i])) @@ -1806,7 +2186,8 @@ public static class FishTranspiler } } - public static CodeInstructions MethodReplacer(this CodeInstructions instructions, T from, V to, bool throwOnFailure = true) where T : Delegate where V : Delegate + public static CodeInstructions MethodReplacer(this CodeInstructions instructions, T from, V to, + bool throwOnFailure = true) where T : Delegate where V : Delegate { Guard.IsNotNull(instructions); Guard.IsNotNull(from); @@ -1815,20 +2196,27 @@ public static class FishTranspiler var success = false; foreach (var instruction in instructions) { - if ((instruction.operand as MethodBase) == from.Method) + if (instruction.operand as MethodBase == from.Method) { instruction.opcode = to.Method.IsStatic ? OpCodes.Call : OpCodes.Callvirt; instruction.operand = to.Method; success = true; } + yield return instruction; } if (throwOnFailure && !success) - UtilityF.Throw($"FishTranspiler.MethodReplacer couldn't find method {to.Method.FullDescription()} for {UtilityF.GetCallingAssembly().GetName().Name}, Version {UtilityF.GetCallingAssembly().GetName().Version}"); + { + ThrowHelper.ThrowInvalidOperationException( + $"FishTranspiler.MethodReplacer couldn't find method {to.Method.FullDescription()} for { + UtilityF.GetCallingAssembly().GetName().Name}, Version { + UtilityF.GetCallingAssembly().GetName().Version}"); + } } - public static CodeInstructions InsertBefore(this CodeInstructions instructions, Predicate predicate, CodeInstruction instructionToInsert, bool throwOnFailure = true) + public static CodeInstructions InsertBefore(this CodeInstructions instructions, + Predicate predicate, CodeInstruction instructionToInsert, bool throwOnFailure = true) { Guard.IsNotNull(instructions); Guard.IsNotNull(predicate); @@ -1837,7 +2225,8 @@ public static class FishTranspiler return instructions.InsertBefore(predicate, new[] { instructionToInsert }, throwOnFailure); } - public static CodeInstructions InsertBefore(this CodeInstructions instructions, Predicate predicate, CodeInstructions instructionsToInsert, bool throwOnFailure = true) + public static CodeInstructions InsertBefore(this CodeInstructions instructions, + Predicate predicate, CodeInstructions instructionsToInsert, bool throwOnFailure = true) { Guard.IsNotNull(instructions); Guard.IsNotNull(predicate); @@ -1850,16 +2239,24 @@ public static class FishTranspiler { foreach (var instruction in instructionsToInsert) yield return instruction; + success = true; } + yield return code; } if (throwOnFailure && !success) - UtilityF.Throw($"FishTranspiler.InsertBefore couldn't find target instruction for {predicate.Method.FullDescription()} of {UtilityF.GetCallingAssembly().GetName().Name}, Version {UtilityF.GetCallingAssembly().GetName().Version}"); + { + ThrowHelper.ThrowInvalidOperationException( + $"FishTranspiler.InsertBefore couldn't find target instruction for { + predicate.Method.FullDescription()} of {UtilityF.GetCallingAssembly().GetName().Name}, Version { + UtilityF.GetCallingAssembly().GetName().Version}"); + } } - public static CodeInstructions InsertAfter(this CodeInstructions instructions, Predicate predicate, CodeInstruction instructionToInsert, bool throwOnFailure = true) + public static CodeInstructions InsertAfter(this CodeInstructions instructions, Predicate predicate, + CodeInstruction instructionToInsert, bool throwOnFailure = true) { Guard.IsNotNull(instructions); Guard.IsNotNull(predicate); @@ -1868,7 +2265,8 @@ public static class FishTranspiler return instructions.InsertAfter(predicate, new[] { instructionToInsert }, throwOnFailure); } - public static CodeInstructions InsertAfter(this CodeInstructions instructions, Predicate predicate, CodeInstructions instructionsToInsert, bool throwOnFailure = true) + public static CodeInstructions InsertAfter(this CodeInstructions instructions, Predicate predicate, + CodeInstructions instructionsToInsert, bool throwOnFailure = true) { Guard.IsNotNull(instructions); Guard.IsNotNull(predicate); @@ -1878,19 +2276,27 @@ public static class FishTranspiler foreach (var code in instructions) { yield return code; - if (predicate(code)) - { - foreach (var instruction in instructionsToInsert) - yield return instruction; - success = true; - } + + if (!predicate(code)) + continue; + + foreach (var instruction in instructionsToInsert) + yield return instruction; + + success = true; } if (throwOnFailure && !success) - UtilityF.Throw($"FishTranspiler.InsertAfter couldn't find target instruction for {predicate.Method.FullDescription()} of {UtilityF.GetCallingAssembly().GetName().Name}, Version {UtilityF.GetCallingAssembly().GetName().Version}"); + { + ThrowHelper.ThrowInvalidOperationException( + $"FishTranspiler.InsertAfter couldn't find target instruction for { + predicate.Method.FullDescription()} of {UtilityF.GetCallingAssembly().GetName().Name}, Version { + UtilityF.GetCallingAssembly().GetName().Version}"); + } } - public static CodeInstructions Replace(this CodeInstructions instructions, Predicate predicate, Func replacement, bool throwOnFailure = true) + public static CodeInstructions Replace(this CodeInstructions instructions, Predicate predicate, + Func replacement, bool throwOnFailure = true) { Guard.IsNotNull(instructions); Guard.IsNotNull(predicate); @@ -1899,7 +2305,8 @@ public static class FishTranspiler return instructions.Replace(predicate, c => new[] { replacement(c) }, throwOnFailure); } - public static CodeInstructions Replace(this CodeInstructions instructions, Predicate predicate, Func replacement, bool throwOnFailure = true) + public static CodeInstructions Replace(this CodeInstructions instructions, Predicate predicate, + Func replacement, bool throwOnFailure = true) { Guard.IsNotNull(instructions); Guard.IsNotNull(predicate); @@ -1912,19 +2319,25 @@ public static class FishTranspiler { foreach (var instruction in replacement(code)) yield return instruction; + success = true; } else - { yield return code; - } } if (throwOnFailure && !success) - UtilityF.Throw($"FishTranspiler.Replace couldn't find target instruction for {predicate.Method.FullDescription()} of {UtilityF.GetCallingAssembly().GetName().Name}, Version {UtilityF.GetCallingAssembly().GetName().Version}"); + { + ThrowHelper.ThrowInvalidOperationException( + $"FishTranspiler.Replace couldn't find target instruction for { + predicate.Method.FullDescription()} of {UtilityF.GetCallingAssembly().GetName().Name}, Version { + UtilityF.GetCallingAssembly().GetName().Version}"); + } } - public static CodeInstructions ReplaceAt(this CodeInstructions instructions, Func, int, bool> position, Func replacement, bool throwOnFailure = true) + public static CodeInstructions ReplaceAt(this CodeInstructions instructions, + Func, int, bool> position, Func replacement, + bool throwOnFailure = true) { Guard.IsNotNull(instructions); Guard.IsNotNull(position); @@ -1933,7 +2346,9 @@ public static class FishTranspiler return instructions.ReplaceAt(position, c => new[] { replacement(c) }, throwOnFailure); } - public static CodeInstructions ReplaceAt(this CodeInstructions instructions, Func, int, bool> position, Func replacement, bool throwOnFailure = true) + public static CodeInstructions ReplaceAt(this CodeInstructions instructions, + Func, int, bool> position, Func replacement, + bool throwOnFailure = true) { Guard.IsNotNull(instructions); Guard.IsNotNull(position); @@ -1943,28 +2358,31 @@ public static class FishTranspiler var codes = instructions as List ?? instructions.ToList(); for (var i = 0; i < codes.Count; i++) { - if (position(codes, i)) - { - var code = codes[i]; - codes.RemoveAt(i); + if (!position(codes, i)) + continue; - var replacementList = replacement(code).ToList(); - codes.InsertRange(i, replacementList); - i += replacementList.Count - 1; - success = true; - } + var code = codes[i]; + codes.RemoveAt(i); + + var replacementList = replacement(code).ToList(); + codes.InsertRange(i, replacementList); + i += replacementList.Count - 1; + success = true; } if (throwOnFailure && !success) { - UtilityF.Throw( - $"FishTranspiler.ReplaceAt couldn't find target instruction for {position.Method.FullDescription()} of {UtilityF.GetCallingAssembly().GetName().Name}, Version {UtilityF.GetCallingAssembly().GetName().Version}"); + ThrowHelper.ThrowInvalidOperationException( + $"FishTranspiler.ReplaceAt couldn't find target instruction for { + position.Method.FullDescription()} of {UtilityF.GetCallingAssembly().GetName().Name}, Version { + UtilityF.GetCallingAssembly().GetName().Version}"); } return codes; } - public static CodeInstruction With(this CodeInstruction instruction, OpCode? opcode = null, object? operand = null, IEnumerable