diff --git a/src/Nino.Core/FastMap.cs b/src/Nino.Core/FastMap.cs index 95962bbf..d5abd843 100644 --- a/src/Nino.Core/FastMap.cs +++ b/src/Nino.Core/FastMap.cs @@ -1,102 +1,360 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Nino.Core { - public sealed class FastMap where TKey : unmanaged, IEquatable + + /// + /// Learn more from: https://github.com/TheLight-233/LuminDelegate + /// + /// + /// + public sealed class FastMap : IDisposable, IEnumerable> + where TKey : notnull, IEquatable { - private TKey[] _keys; - private TValue[] _values; + private const int MaxKickCount = 16; + private const int MinCapacity = 8; + + private struct Entry + { + public uint HashCode; + public TKey Key; + public TValue Value; + public bool IsOccupied; + } + + private Entry[] _table1; + private Entry[] _table2; + private int _capacity; private int _count; + private int _capacityMask; + private int _version; + + public int Count => _count; + public int Capacity => _capacity; + public bool IsCreated => _table1 != null; + + public ref TValue this[in TKey key] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + var hashCode = key.GetHashCode(); + hashCode ^= hashCode >> 16; + + var index1 = hashCode & _capacityMask; + ref Entry entry1 = ref _table1[index1]; + if (entry1.HashCode == hashCode && EqualityComparer.Default.Equals(entry1.Key, key)) + return ref entry1.Value; + + var index2 = (hashCode >> 8) & _capacityMask; + ref Entry entry2 = ref _table2[index2]; + if (entry2.HashCode == hashCode && EqualityComparer.Default.Equals(entry2.Key, key)) + return ref entry2.Value; + throw new KeyNotFoundException(); + } + } - public FastMap(int capacity = 16) + public FastMap(int capacity) { - int safeCap = Math.Max(4, capacity); - _keys = new TKey[safeCap]; - _values = new TValue[safeCap]; + if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity)); + _capacity = CalculateCapacity(capacity); _count = 0; + _version = 0; + InitializeTables(); } - public ReadOnlySpan Keys => _keys.AsSpan(0, _count); - - public ReadOnlySpan Values => _values.AsSpan(0, _count); + public FastMap() : this(0) + { + } - public int Count + public FastMap(in FastMap source) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _count; + if (source == null) throw new ArgumentNullException(nameof(source)); + + _capacity = source._capacity; + _count = source._count; + _capacityMask = source._capacityMask; + _version = source._version; + _table1 = new Entry[_capacity]; + _table2 = new Entry[_capacity]; + + Array.Copy(source._table1, _table1, _capacity); + Array.Copy(source._table2, _table2, _capacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(TKey key, TValue value) + private static int CalculateCapacity(int capacity) { - int index = CustomBinarySearch(key); - if (index >= 0) + if (capacity < MinCapacity) return MinCapacity; + capacity--; + capacity |= capacity >> 1; + capacity |= capacity >> 2; + capacity |= capacity >> 4; + capacity |= capacity >> 8; + capacity |= capacity >> 16; + return capacity + 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void InitializeTables() + { + _capacityMask = _capacity - 1; + _table1 = new Entry[_capacity]; + _table2 = new Entry[_capacity]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(in TKey key, in TValue value) + { + TryAddOrUpdate(key, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref TValue FirstValue() + { + for (int i = 0; i < _capacity; i++) { - // Key already exists, update the value - _values[index] = value; - return; + ref Entry entry1 = ref _table1[i]; + if (entry1.IsOccupied) + return ref entry1.Value; + + ref Entry entry2 = ref _table2[i]; + if (entry2.IsOccupied) + return ref entry2.Value; } - int insertAt = ~index; + throw new InvalidOperationException("The FastMap is empty."); + } - if (_count >= _keys.Length) - Grow(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryAdd(in TKey key, in TValue value) + { + var hashCode = key.GetHashCode(); + hashCode ^= hashCode >> 16; - if (insertAt < _count) + var index1 = hashCode & _capacityMask; + ref Entry entry1 = ref _table1[index1]; + if (!entry1.IsOccupied) { - Array.Copy(_keys, insertAt, _keys, insertAt + 1, _count - insertAt); - Array.Copy(_values, insertAt, _values, insertAt + 1, _count - insertAt); + entry1.HashCode = (uint)hashCode; + entry1.Key = key; + entry1.Value = value; + entry1.IsOccupied = true; + _count++; + _version++; + return true; } + else if (entry1.HashCode == hashCode && EqualityComparer.Default.Equals(entry1.Key, key)) + return false; - _keys[insertAt] = key; - _values[insertAt] = value; - _count++; + var index2 = (hashCode >> 8) & _capacityMask; + ref Entry entry2 = ref _table2[index2]; + if (!entry2.IsOccupied) + { + entry2.HashCode = (uint)hashCode; + entry2.Key = key; + entry2.Value = value; + entry2.IsOccupied = true; + _count++; + _version++; + return true; + } + else if (entry2.HashCode == hashCode && EqualityComparer.Default.Equals(entry2.Key, key)) + return false; + + bool res = CuckooInsert(hashCode, key, value, false); + if (res) _version++; + return res; } - public void Remove(TKey key) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool TryAddOrUpdate(in TKey key, in TValue value) { - if (_count == 0) - return; + var hashCode = key.GetHashCode(); + hashCode ^= hashCode >> 16; - int index = CustomBinarySearch(key); - if (index < 0) - return; // Key not found + var index1 = hashCode & _capacityMask; + ref Entry entry1 = ref _table1[index1]; + if (!entry1.IsOccupied) + { + entry1.HashCode = (uint)hashCode; + entry1.Key = key; + entry1.Value = value; + entry1.IsOccupied = true; + _count++; + _version++; + return true; + } + else if (entry1.HashCode == hashCode && EqualityComparer.Default.Equals(entry1.Key, key)) + { + entry1.Value = value; + _version++; + return true; + } - // Shift elements to remove the key - if (index < _count - 1) + var index2 = (hashCode >> 8) & _capacityMask; + ref Entry entry2 = ref _table2[index2]; + if (!entry2.IsOccupied) { - Array.Copy(_keys, index + 1, _keys, index, _count - index - 1); - Array.Copy(_values, index + 1, _values, index, _count - index - 1); + entry2.HashCode = (uint)hashCode; + entry2.Key = key; + entry2.Value = value; + entry2.IsOccupied = true; + _count++; + _version++; + return true; + } + else if (entry2.HashCode == hashCode && EqualityComparer.Default.Equals(entry2.Key, key)) + { + entry2.Value = value; + _version++; + return true; } - _keys[--_count] = default; // Clear the last element - _values[_count] = default; + bool res = CuckooInsert(hashCode, key, value, true); + if (res) _version++; + return res; } - private void Grow() + [MethodImpl(MethodImplOptions.NoInlining)] + private bool CuckooInsert(int hashCode, in TKey key, in TValue value, bool updateIfExists) { - int newSize = Math.Max(4, _keys.Length * 2); - Array.Resize(ref _keys, newSize); - Array.Resize(ref _values, newSize); + int currentHashCode = hashCode; + TKey currentKey = key; + TValue currentValue = value; + int tableIndex = 0; + + for (int i = 0; i < MaxKickCount; i++) + { + if (tableIndex == 0) + { + var index = currentHashCode & _capacityMask; + ref Entry entry = ref _table1[index]; + if (!entry.IsOccupied) + { + entry.HashCode = (uint)currentHashCode; + entry.Key = currentKey; + entry.Value = currentValue; + entry.IsOccupied = true; + _count++; + return true; + } + else if (updateIfExists && entry.HashCode == currentHashCode && EqualityComparer.Default.Equals(entry.Key, currentKey)) + { + entry.Value = currentValue; + return true; + } + + (currentHashCode, entry.HashCode) = ((int)entry.HashCode, (uint)currentHashCode); + (currentKey, entry.Key) = (entry.Key, currentKey); + (currentValue, entry.Value) = (entry.Value, currentValue); + tableIndex = 1; + } + else + { + var index = (currentHashCode >> 8) & _capacityMask; + ref Entry entry = ref _table2[index]; + if (!entry.IsOccupied) + { + entry.HashCode = (uint)currentHashCode; + entry.Key = currentKey; + entry.Value = currentValue; + entry.IsOccupied = true; + _count++; + return true; + } + else if (updateIfExists && entry.HashCode == currentHashCode && EqualityComparer.Default.Equals(entry.Key, currentKey)) + { + entry.Value = currentValue; + return true; + } + + (currentHashCode, entry.HashCode) = ((int)entry.HashCode, (uint)currentHashCode); + (currentKey, entry.Key) = (entry.Key, currentKey); + (currentValue, entry.Value) = (entry.Value, currentValue); + tableIndex = 0; + } + } + + Resize(); + return updateIfExists ? TryAddOrUpdate(currentKey, currentValue) : TryAdd(currentKey, currentValue); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetValue(TKey key, out TValue value) + public ref TValue GetValueRefOrAddDefault(TKey key, out bool exists) { - if (_count == 0) + var hashCode = key.GetHashCode(); + hashCode ^= hashCode >> 16; + + var index1 = hashCode & _capacityMask; + ref Entry entry1 = ref _table1[index1]; + if (entry1.HashCode == hashCode && EqualityComparer.Default.Equals(entry1.Key, key)) { - value = default!; - return false; + exists = true; + return ref entry1.Value; + } + + var index2 = (hashCode >> 8) & _capacityMask; + + ref Entry entry2 = ref _table2[index2]; + if (entry2.HashCode == hashCode && EqualityComparer.Default.Equals(entry2.Key, key)) + { + exists = true; + return ref entry2.Value; + } + + exists = false; + + if (!entry1.IsOccupied) + { + entry1.HashCode = (uint)hashCode; + entry1.Key = key; + entry1.Value = default; + entry1.IsOccupied = true; + _count++; + _version++; + return ref entry1.Value; } + else if (!entry2.IsOccupied) + { + entry2.HashCode = (uint)hashCode; + entry2.Key = key; + entry2.Value = default; + entry2.IsOccupied = true; + _count++; + _version++; + return ref entry2.Value; + } + else + { + if (!TryAdd(key, default)) + throw new InvalidOperationException("Failed to add key to dictionary"); + return ref GetValueRefOrAddDefault(key, out exists); + } + } - // For typical subtype counts (10-200), linear search is often faster - // than binary search due to better cache locality and branch prediction - int index = _count <= 64 ? LinearSearch(key) : BinarySearch(key); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetValue(in TKey key, out TValue value) + { + var hashCode = key.GetHashCode(); + hashCode ^= hashCode >> 16; + + var index1 = hashCode & _capacityMask; + ref Entry entry1 = ref _table1[index1]; + if (entry1.HashCode == hashCode && EqualityComparer.Default.Equals(entry1.Key, key)) + { + value = entry1.Value; + return true; + } - if (index >= 0) + var index2 = (hashCode >> 8) & _capacityMask; + ref Entry entry2 = ref _table2[index2]; + if (entry2.HashCode == hashCode && EqualityComparer.Default.Equals(entry2.Key, key)) { - value = _values[index]; + value = entry2.Value; return true; } @@ -105,166 +363,225 @@ public bool TryGetValue(TKey key, out TValue value) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool ContainsKey(TKey key) => - _count <= 64 ? LinearSearch(key) >= 0 : BinarySearch(key) >= 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int BinarySearch(TKey key) + public ref TValue GetValueRef(in TKey key) { - return CustomBinarySearch(key); + var hashCode = key.GetHashCode(); + hashCode ^= hashCode >> 16; + + var index1 = hashCode & _capacityMask; + ref Entry entry1 = ref _table1[index1]; + if (entry1.HashCode == hashCode && EqualityComparer.Default.Equals(entry1.Key, key)) + return ref entry1.Value; + + var index2 = (hashCode >> 8) & _capacityMask; + ref Entry entry2 = ref _table2[index2]; + if (entry2.HashCode == hashCode && EqualityComparer.Default.Equals(entry2.Key, key)) + return ref entry2.Value; + + throw new KeyNotFoundException(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CustomBinarySearch(TKey key) + public bool ContainsKey(in TKey key) { - if (_count == 0) - return ~0; + var hashCode = key.GetHashCode(); + hashCode ^= hashCode >> 16; - int left = 0; - int right = _count - 1; + var index1 = hashCode & _capacityMask; + ref Entry entry1 = ref _table1[index1]; + if (entry1.HashCode == hashCode && EqualityComparer.Default.Equals(entry1.Key, key)) + return true; - while (left <= right) - { - int mid = left + (right - left) / 2; - var midKey = _keys[mid]; + var index2 = (hashCode >> 8) & _capacityMask; + ref Entry entry2 = ref _table2[index2]; + return entry2.HashCode == hashCode && EqualityComparer.Default.Equals(entry2.Key, key); + } - int comparison = CompareKeys(midKey, key); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ContainsValue(in TValue value) + { + var comparer = EqualityComparer.Default; + for (int i = 0; i < _capacity; i++) + { + ref Entry entry1 = ref _table1[i]; + if (entry1.IsOccupied && comparer.Equals(entry1.Value, value)) + return true; - if (comparison == 0) - return mid; - if (comparison < 0) - left = mid + 1; - else - right = mid - 1; + ref Entry entry2 = ref _table2[i]; + if (entry2.IsOccupied && comparer.Equals(entry2.Value, value)) + return true; } - return ~left; + return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int CompareKeys(TKey left, TKey right) + public bool Remove(TKey key) { - // Handle specific key types without requiring IComparable - if (typeof(TKey) == typeof(IntPtr)) - { - var leftPtr = Unsafe.As(ref left); - var rightPtr = Unsafe.As(ref right); - return leftPtr.ToInt64().CompareTo(rightPtr.ToInt64()); - } + var hashCode = key.GetHashCode(); + hashCode ^= hashCode >> 16; - if (typeof(TKey) == typeof(int)) + var index1 = hashCode & _capacityMask; + ref Entry entry1 = ref _table1[index1]; + if (entry1.HashCode == hashCode && EqualityComparer.Default.Equals(entry1.Key, key)) { - var leftInt = Unsafe.As(ref left); - var rightInt = Unsafe.As(ref right); - return leftInt.CompareTo(rightInt); + entry1.IsOccupied = false; + _count--; + _version++; + return true; } - if (typeof(TKey) == typeof(uint)) + var index2 = (hashCode >> 8) & _capacityMask; + ref Entry entry2 = ref _table2[index2]; + if (entry2.HashCode == hashCode && EqualityComparer.Default.Equals(entry2.Key, key)) { - var leftUint = Unsafe.As(ref left); - var rightUint = Unsafe.As(ref right); - return leftUint.CompareTo(rightUint); + entry2.IsOccupied = false; + _count--; + _version++; + return true; } - if (typeof(TKey) == typeof(long)) - { - var leftLong = Unsafe.As(ref left); - var rightLong = Unsafe.As(ref right); - return leftLong.CompareTo(rightLong); - } + return false; + } - if (typeof(TKey) == typeof(ulong)) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + if (_count > 0) { - var leftUlong = Unsafe.As(ref left); - var rightUlong = Unsafe.As(ref right); - return leftUlong.CompareTo(rightUlong); + Array.Clear(_table1, 0, _capacity); + Array.Clear(_table2, 0, _capacity); + _count = 0; + _version++; } + } - // Fallback for other types - this should work for IComparable types - return ((IComparable)left).CompareTo(right); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void EnsureCapacity(int capacity) + { + if (capacity > _capacity) + Resize(CalculateCapacity(capacity)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int LinearSearch(TKey key) + public void TrimExcess() { - // Optimized linear search avoiding Equals() call overhead - var keys = _keys; - int count = _count; - // Fast path for common key types - avoid virtual/interface calls - if (typeof(TKey) == typeof(IntPtr)) - { - var targetKey = Unsafe.As(ref key); - var intPtrKeys = Unsafe.As(ref keys); - for (int i = 0; i < count; i++) - { - if (intPtrKeys[i] == targetKey) - return i; - } + int newCapacity = CalculateCapacity(_count); - return -1; - } + if (newCapacity < _capacity) + Resize(newCapacity); + } - if (typeof(TKey) == typeof(int)) - { - var targetKey = Unsafe.As(ref key); - var intKeys = Unsafe.As(ref keys); - for (int i = 0; i < count; i++) - { - if (intKeys[i] == targetKey) - return i; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } - return -1; - } + IEnumerator> IEnumerable>.GetEnumerator() => + GetEnumerator(); - if (typeof(TKey) == typeof(uint)) - { - var targetKey = Unsafe.As(ref key); - var uintKeys = Unsafe.As(ref keys); - for (int i = 0; i < count; i++) - { - if (uintKeys[i] == targetKey) - return i; - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - return -1; - } + public void Dispose() + { + _table1 = null; + _table2 = null; + _count = 0; + _capacity = 0; + _capacityMask = 0; + _version = 0; + } - if (typeof(TKey) == typeof(long)) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Resize() => Resize(_capacity * 2); + + [MethodImpl(MethodImplOptions.NoInlining)] + private void Resize(int newCapacity) + { + Entry[] oldTable1 = _table1; + Entry[] oldTable2 = _table2; + int oldCapacity = _capacity; + int oldCount = _count; + _capacity = newCapacity; + _count = 0; + InitializeTables(); + if (oldCount > 0) { - var targetKey = Unsafe.As(ref key); - var longKeys = Unsafe.As(ref keys); - for (int i = 0; i < count; i++) + for (int i = 0; i < oldCapacity; i++) { - if (longKeys[i] == targetKey) - return i; + ref Entry oldEntry1 = ref oldTable1[i]; + if (oldEntry1.IsOccupied) + if (!TryAdd(oldEntry1.Key, oldEntry1.Value)) + throw new InvalidOperationException("Failed to rehash during resize"); + ref Entry oldEntry2 = ref oldTable2[i]; + if (oldEntry2.IsOccupied) + if (!TryAdd(oldEntry2.Key, oldEntry2.Value)) + throw new InvalidOperationException("Failed to rehash during resize"); } + } + } - return -1; + public struct Enumerator : IEnumerator> + { + private readonly FastMap _dict; + private int _index; + private int _table; + private KeyValuePair _current; + private readonly int _version; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Enumerator(FastMap dictionary) + { + _dict = dictionary; + _index = -1; + _table = 0; + _current = default; + _version = dictionary._version; } - if (typeof(TKey) == typeof(ulong)) + public KeyValuePair Current => _current; + object IEnumerator.Current => Current; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() { - var targetKey = Unsafe.As(ref key); - var ulongKeys = Unsafe.As(ref keys); - for (int i = 0; i < count; i++) + if (_version != _dict._version) + throw new InvalidOperationException("Collection was modified"); + while (_table < 2) { - if (ulongKeys[i] == targetKey) - return i; + var table = _table == 0 ? _dict._table1 : _dict._table2; + while (++_index < _dict._capacity) + { + ref Entry entry = ref table[_index]; + if (entry.IsOccupied) + { + _current = new KeyValuePair(entry.Key, entry.Value); + return true; + } + } + + _table++; + _index = -1; } - return -1; + _current = default; + return false; } - // Fallback for other types - for (int i = 0; i < count; i++) + public void Reset() { - if (keys[i].Equals(key)) - return i; + if (_version != _dict._version) + throw new InvalidOperationException("Collection was modified"); + _index = -1; + _table = 0; + _current = default; } - return -1; + public void Dispose() + { + } } } } diff --git a/src/Nino.Core/NinoSerializer.cs b/src/Nino.Core/NinoSerializer.cs index 4dea376b..5e7a79fa 100644 --- a/src/Nino.Core/NinoSerializer.cs +++ b/src/Nino.Core/NinoSerializer.cs @@ -300,7 +300,7 @@ public static unsafe void SerializePolymorphic(T val, ref Writer writer) if (SubTypeSerializers.Count == 1) { - SubTypeSerializers.Values[0](val, ref writer); + SubTypeSerializers.FirstValue()(val, ref writer); return; } diff --git a/src/Nino.Generator/BuiltInType/NinoBuiltInTypesGenerator.cs b/src/Nino.Generator/BuiltInType/NinoBuiltInTypesGenerator.cs index 0b413956..8a912549 100644 --- a/src/Nino.Generator/BuiltInType/NinoBuiltInTypesGenerator.cs +++ b/src/Nino.Generator/BuiltInType/NinoBuiltInTypesGenerator.cs @@ -110,7 +110,7 @@ protected override void Generate(SourceProductionContext spc) { var typeName = registeredType.GetDisplayString(); registrationCode.AppendLine( - $" NinoTypeMetadata.RegisterSerializer<{typeName}>(Serializer.Serialize, Serializer.Serialize, false);"); + $" NinoTypeMetadata.RegisterSerializer<{typeName}>(Serializer.Serialize, false);"); registrationCode.AppendLine( $" NinoTypeMetadata.RegisterDeserializer<{typeName}>(-1, Deserializer.Deserialize, Deserializer.DeserializeRef, Deserializer.Deserialize, Deserializer.DeserializeRef, false);"); } diff --git a/src/Nino.Generator/Common/SerializerGenerator.Trivial.cs b/src/Nino.Generator/Common/SerializerGenerator.Trivial.cs index f4ec5d84..90a42e8c 100644 --- a/src/Nino.Generator/Common/SerializerGenerator.Trivial.cs +++ b/src/Nino.Generator/Common/SerializerGenerator.Trivial.cs @@ -206,7 +206,8 @@ private bool TryGetInlineSerializeCall(ITypeSymbol type, string valueExpression, return true; } - private void WriteMembers(NinoType type, string valName, StringBuilder sb, SourceProductionContext spc, string indent = "") + private void WriteMembers(NinoType type, string valName, StringBuilder sb, SourceProductionContext spc, + string indent = "") { // First pass: collect all types that need serializers or custom formatters HashSet typesNeedingSerializers = new(SymbolEqualityComparer.Default); @@ -376,15 +377,18 @@ string GetSerializerVarName(ITypeSymbol serializerType) default: // PRIORITY 6: Invalid/unrecognizable type - report warning - sb.AppendLine($"{indent} // WARNING: Member '{member.Name}' of type '{declaredType.GetDisplayString()}' cannot be serialized (unrecognizable type)"); + sb.AppendLine( + $"{indent} // WARNING: Member '{member.Name}' of type '{declaredType.GetDisplayString()}' cannot be serialized (unrecognizable type)"); // Only report the warning once to avoid duplicates between serializer and deserializer if (!member.HasReportedUnrecognizableTypeWarning) { // Check if member is from current compilation to determine location var memberAssembly = member.MemberSymbol.ContainingType.ContainingAssembly; - var isCurrentAssembly = SymbolEqualityComparer.Default.Equals(memberAssembly, Compilation.Assembly); + var isCurrentAssembly = + SymbolEqualityComparer.Default.Equals(memberAssembly, Compilation.Assembly); var diagnosticLocation = isCurrentAssembly - ? (member.MemberSymbol.Locations.FirstOrDefault() ?? type.TypeSymbol.Locations.FirstOrDefault() ?? Location.None) + ? (member.MemberSymbol.Locations.FirstOrDefault() ?? + type.TypeSymbol.Locations.FirstOrDefault() ?? Location.None) : Location.None; spc.ReportDiagnostic(Diagnostic.Create( @@ -399,6 +403,7 @@ string GetSerializerVarName(ITypeSymbol serializerType) type.TypeSymbol.GetDisplayString())); member.HasReportedUnrecognizableTypeWarning = true; } + break; } }