diff --git a/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder+PrivateMarshal.cs b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder+PrivateMarshal.cs index f2d3c6cff5f6e..01f7a1c796de2 100644 --- a/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder+PrivateMarshal.cs +++ b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder+PrivateMarshal.cs @@ -15,7 +15,7 @@ internal static class PrivateMarshal { /// public static ref TValue FindValue(Builder dictionary, TKey key) - => ref SegmentedCollectionsMarshal.GetValueRefOrNullRef(dictionary.GetOrCreateMutableDictionary(), key); + => ref SegmentedCollectionsMarshal.GetValueRefOrNullRef(dictionary._builder.GetOrCreateMutableDictionary(), key); } } } diff --git a/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder.cs b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder.cs index c813f54169bb4..6e36d573d4a10 100644 --- a/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder.cs +++ b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+Builder.cs @@ -6,6 +6,7 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.Collections.Internal; namespace Microsoft.CodeAnalysis.Collections { @@ -14,50 +15,26 @@ internal readonly partial struct ImmutableSegmentedDictionary public sealed partial class Builder : IDictionary, IReadOnlyDictionary, IDictionary { /// - /// The immutable collection this builder is based on. + /// The private builder implementation. /// - private ImmutableSegmentedDictionary _dictionary; - - /// - /// The current mutable collection this builder is operating on. This field is initialized to a copy of - /// the first time a change is made. - /// - private SegmentedDictionary? _mutableDictionary; + private ValueBuilder _builder; internal Builder(ImmutableSegmentedDictionary dictionary) - { - _dictionary = dictionary; - } + => _builder = new ValueBuilder(dictionary); public IEqualityComparer KeyComparer { - get - { - return ReadOnlyDictionary.Comparer; - } - - set - { - if (value is null) - throw new ArgumentNullException(nameof(value)); - - if (value != KeyComparer) - { - // Rewrite the mutable dictionary using a new comparer - var valuesToAdd = ReadOnlyDictionary; - _mutableDictionary = new SegmentedDictionary(value); - AddRange(valuesToAdd); - } - } + get => _builder.KeyComparer; + set => _builder.KeyComparer = value; } - public int Count => ReadOnlyDictionary.Count; + public int Count => _builder.Count; public KeyCollection Keys => new(this); public ValueCollection Values => new(this); - private SegmentedDictionary ReadOnlyDictionary => _mutableDictionary ?? _dictionary._dictionary; + private SegmentedDictionary ReadOnlyDictionary => _builder.ReadOnlyDictionary; IEnumerable IReadOnlyDictionary.Keys => Keys; @@ -67,181 +44,105 @@ public IEqualityComparer KeyComparer ICollection IDictionary.Values => Values; - bool ICollection>.IsReadOnly => false; + bool ICollection>.IsReadOnly => ICollectionCalls>.IsReadOnly(ref _builder); ICollection IDictionary.Keys => Keys; ICollection IDictionary.Values => Values; - bool IDictionary.IsReadOnly => false; + bool IDictionary.IsReadOnly => IDictionaryCalls.IsReadOnly(ref _builder); - bool IDictionary.IsFixedSize => false; + bool IDictionary.IsFixedSize => IDictionaryCalls.IsFixedSize(ref _builder); object ICollection.SyncRoot => this; - bool ICollection.IsSynchronized => false; + bool ICollection.IsSynchronized => ICollectionCalls.IsSynchronized(ref _builder); public TValue this[TKey key] { - get => ReadOnlyDictionary[key]; - set => GetOrCreateMutableDictionary()[key] = value; + get => _builder[key]; + set => _builder[key] = value; } object? IDictionary.this[object key] { - get => ((IDictionary)ReadOnlyDictionary)[key]; - set => ((IDictionary)GetOrCreateMutableDictionary())[key] = value; - } - - private SegmentedDictionary GetOrCreateMutableDictionary() - { - return _mutableDictionary ??= new SegmentedDictionary(_dictionary._dictionary, _dictionary.KeyComparer); + get => IDictionaryCalls.GetItem(ref _builder, key); + set => IDictionaryCalls.SetItem(ref _builder, key, value); } public void Add(TKey key, TValue value) - { - if (Contains(new KeyValuePair(key, value))) - return; - - GetOrCreateMutableDictionary().Add(key, value); - } + => _builder.Add(key, value); public void Add(KeyValuePair item) - => Add(item.Key, item.Value); + => _builder.Add(item); public void AddRange(IEnumerable> items) - { - if (items == null) - throw new ArgumentNullException(nameof(items)); - - foreach (var pair in items) - Add(pair.Key, pair.Value); - } + => _builder.AddRange(items); public void Clear() - { - if (ReadOnlyDictionary.Count != 0) - { - if (_mutableDictionary is null) - _mutableDictionary = new SegmentedDictionary(KeyComparer); - else - _mutableDictionary.Clear(); - } - } + => _builder.Clear(); public bool Contains(KeyValuePair item) - { - return TryGetValue(item.Key, out var value) - && EqualityComparer.Default.Equals(value, item.Value); - } + => _builder.Contains(item); public bool ContainsKey(TKey key) - => ReadOnlyDictionary.ContainsKey(key); + => _builder.ContainsKey(key); public bool ContainsValue(TValue value) - { - return _dictionary.ContainsValue(value); - } + => _builder.ContainsValue(value); public Enumerator GetEnumerator() - => new(GetOrCreateMutableDictionary(), Enumerator.ReturnType.KeyValuePair); + => _builder.GetEnumerator(); public TValue? GetValueOrDefault(TKey key) - { - if (TryGetValue(key, out var value)) - return value; - - return default; - } + => _builder.GetValueOrDefault(key); public TValue GetValueOrDefault(TKey key, TValue defaultValue) - { - if (TryGetValue(key, out var value)) - return value; - - return defaultValue; - } + => _builder.GetValueOrDefault(key, defaultValue); public bool Remove(TKey key) - { - if (_mutableDictionary is null && !ContainsKey(key)) - return false; - - return GetOrCreateMutableDictionary().Remove(key); - } + => _builder.Remove(key); public bool Remove(KeyValuePair item) - { - if (!Contains(item)) - { - return false; - } - - GetOrCreateMutableDictionary().Remove(item.Key); - return true; - } + => _builder.Remove(item); public void RemoveRange(IEnumerable keys) - { - if (keys is null) - throw new ArgumentNullException(nameof(keys)); - - foreach (var key in keys) - { - Remove(key); - } - } + => _builder.RemoveRange(keys); public bool TryGetKey(TKey equalKey, out TKey actualKey) - { - foreach (var key in Keys) - { - if (KeyComparer.Equals(key, equalKey)) - { - actualKey = key; - return true; - } - } - - actualKey = equalKey; - return false; - } + => _builder.TryGetKey(equalKey, out actualKey); #pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) #pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). - => ReadOnlyDictionary.TryGetValue(key, out value); + => _builder.TryGetValue(key, out value); public ImmutableSegmentedDictionary ToImmutable() - { - _dictionary = new ImmutableSegmentedDictionary(ReadOnlyDictionary); - _mutableDictionary = null; - return _dictionary; - } + => _builder.ToImmutable(); void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) - => ((ICollection>)ReadOnlyDictionary).CopyTo(array, arrayIndex); + => ICollectionCalls>.CopyTo(ref _builder, array, arrayIndex); IEnumerator> IEnumerable>.GetEnumerator() - => new Enumerator(GetOrCreateMutableDictionary(), Enumerator.ReturnType.KeyValuePair); + => IEnumerableCalls>.GetEnumerator(ref _builder); IEnumerator IEnumerable.GetEnumerator() - => new Enumerator(GetOrCreateMutableDictionary(), Enumerator.ReturnType.KeyValuePair); + => IEnumerableCalls.GetEnumerator(ref _builder); bool IDictionary.Contains(object key) - => ((IDictionary)ReadOnlyDictionary).Contains(key); + => IDictionaryCalls.Contains(ref _builder, key); void IDictionary.Add(object key, object? value) - => ((IDictionary)GetOrCreateMutableDictionary()).Add(key, value); + => IDictionaryCalls.Add(ref _builder, key, value); IDictionaryEnumerator IDictionary.GetEnumerator() - => new Enumerator(GetOrCreateMutableDictionary(), Enumerator.ReturnType.DictionaryEntry); + => IDictionaryCalls.GetEnumerator(ref _builder); void IDictionary.Remove(object key) - => ((IDictionary)GetOrCreateMutableDictionary()).Remove(key); + => IDictionaryCalls.Remove(ref _builder, key); void ICollection.CopyTo(Array array, int index) - => ((ICollection)ReadOnlyDictionary).CopyTo(array, index); + => ICollectionCalls.CopyTo(ref _builder, array, index); internal TestAccessor GetTestAccessor() => new TestAccessor(this); @@ -249,7 +150,7 @@ internal TestAccessor GetTestAccessor() internal readonly struct TestAccessor(Builder instance) { internal SegmentedDictionary GetOrCreateMutableDictionary() - => instance.GetOrCreateMutableDictionary(); + => instance._builder.GetOrCreateMutableDictionary(); } } } diff --git a/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+ValueBuilder.cs b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+ValueBuilder.cs new file mode 100644 index 0000000000000..4c20bfdc65cbb --- /dev/null +++ b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2+ValueBuilder.cs @@ -0,0 +1,259 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.Collections +{ + internal readonly partial struct ImmutableSegmentedDictionary + { + private struct ValueBuilder : IDictionary, IReadOnlyDictionary, IDictionary + { + /// + /// The immutable collection this builder is based on. + /// + private ImmutableSegmentedDictionary _dictionary; + + /// + /// The current mutable collection this builder is operating on. This field is initialized to a copy of + /// the first time a change is made. + /// + private SegmentedDictionary? _mutableDictionary; + + internal ValueBuilder(ImmutableSegmentedDictionary dictionary) + { + _dictionary = dictionary; + _mutableDictionary = null; + } + + public IEqualityComparer KeyComparer + { + readonly get + { + return ReadOnlyDictionary.Comparer; + } + + set + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + if (value != KeyComparer) + { + // Rewrite the mutable dictionary using a new comparer + var valuesToAdd = ReadOnlyDictionary; + _mutableDictionary = new SegmentedDictionary(value); + _dictionary = default; + AddRange(valuesToAdd); + } + } + } + + public readonly int Count => ReadOnlyDictionary.Count; + + internal readonly SegmentedDictionary ReadOnlyDictionary => _mutableDictionary ?? _dictionary._dictionary; + + readonly IEnumerable IReadOnlyDictionary.Keys => throw new NotSupportedException(); + + readonly IEnumerable IReadOnlyDictionary.Values => throw new NotSupportedException(); + + readonly ICollection IDictionary.Keys => throw new NotSupportedException(); + + readonly ICollection IDictionary.Values => throw new NotSupportedException(); + + readonly bool ICollection>.IsReadOnly => false; + + readonly ICollection IDictionary.Keys => throw new NotSupportedException(); + + readonly ICollection IDictionary.Values => throw new NotSupportedException(); + + readonly bool IDictionary.IsReadOnly => false; + + readonly bool IDictionary.IsFixedSize => false; + + readonly object ICollection.SyncRoot => throw new NotSupportedException(); + + readonly bool ICollection.IsSynchronized => false; + + public TValue this[TKey key] + { + readonly get => ReadOnlyDictionary[key]; + set => GetOrCreateMutableDictionary()[key] = value; + } + + object? IDictionary.this[object key] + { + readonly get => ((IDictionary)ReadOnlyDictionary)[key]; + set => ((IDictionary)GetOrCreateMutableDictionary())[key] = value; + } + + internal SegmentedDictionary GetOrCreateMutableDictionary() + { + if (_mutableDictionary is null) + { + var originalDictionary = RoslynImmutableInterlocked.InterlockedExchange(ref _dictionary, default); + if (originalDictionary.IsDefault) + throw new InvalidOperationException($"Unexpected concurrent access to {GetType()}"); + + _mutableDictionary = new SegmentedDictionary(originalDictionary._dictionary, originalDictionary.KeyComparer); + } + + return _mutableDictionary; + } + + public void Add(TKey key, TValue value) + { + if (Contains(new KeyValuePair(key, value))) + return; + + GetOrCreateMutableDictionary().Add(key, value); + } + + public void Add(KeyValuePair item) + => Add(item.Key, item.Value); + + public void AddRange(IEnumerable> items) + { + if (items == null) + throw new ArgumentNullException(nameof(items)); + + foreach (var pair in items) + Add(pair.Key, pair.Value); + } + + public void Clear() + { + if (ReadOnlyDictionary.Count != 0) + { + if (_mutableDictionary is null) + { + _mutableDictionary = new SegmentedDictionary(KeyComparer); + _dictionary = default; + } + else + { + _mutableDictionary.Clear(); + } + } + } + + public readonly bool Contains(KeyValuePair item) + { + return TryGetValue(item.Key, out var value) + && EqualityComparer.Default.Equals(value, item.Value); + } + + public readonly bool ContainsKey(TKey key) + => ReadOnlyDictionary.ContainsKey(key); + + public readonly bool ContainsValue(TValue value) + => ReadOnlyDictionary.ContainsValue(value); + + public Enumerator GetEnumerator() + => new(GetOrCreateMutableDictionary(), Enumerator.ReturnType.KeyValuePair); + + public readonly TValue? GetValueOrDefault(TKey key) + { + if (TryGetValue(key, out var value)) + return value; + + return default; + } + + public readonly TValue GetValueOrDefault(TKey key, TValue defaultValue) + { + if (TryGetValue(key, out var value)) + return value; + + return defaultValue; + } + + public bool Remove(TKey key) + { + if (_mutableDictionary is null && !ContainsKey(key)) + return false; + + return GetOrCreateMutableDictionary().Remove(key); + } + + public bool Remove(KeyValuePair item) + { + if (!Contains(item)) + { + return false; + } + + GetOrCreateMutableDictionary().Remove(item.Key); + return true; + } + + public void RemoveRange(IEnumerable keys) + { + if (keys is null) + throw new ArgumentNullException(nameof(keys)); + + foreach (var key in keys) + { + Remove(key); + } + } + +#pragma warning disable IDE0251 // Make member 'readonly' (false positive: https://github.com/dotnet/roslyn/issues/72335) + public bool TryGetKey(TKey equalKey, out TKey actualKey) +#pragma warning restore IDE0251 // Make member 'readonly' + { + foreach (var pair in this) + { + if (KeyComparer.Equals(pair.Key, equalKey)) + { + actualKey = pair.Key; + return true; + } + } + + actualKey = equalKey; + return false; + } + +#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). + public readonly bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) +#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). + => ReadOnlyDictionary.TryGetValue(key, out value); + + public ImmutableSegmentedDictionary ToImmutable() + { + _dictionary = new ImmutableSegmentedDictionary(ReadOnlyDictionary); + _mutableDictionary = null; + return _dictionary; + } + + readonly void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + => ((ICollection>)ReadOnlyDictionary).CopyTo(array, arrayIndex); + + IEnumerator> IEnumerable>.GetEnumerator() + => new Enumerator(GetOrCreateMutableDictionary(), Enumerator.ReturnType.KeyValuePair); + + IEnumerator IEnumerable.GetEnumerator() + => new Enumerator(GetOrCreateMutableDictionary(), Enumerator.ReturnType.KeyValuePair); + + readonly bool IDictionary.Contains(object key) + => ((IDictionary)ReadOnlyDictionary).Contains(key); + + void IDictionary.Add(object key, object? value) + => ((IDictionary)GetOrCreateMutableDictionary()).Add(key, value); + + IDictionaryEnumerator IDictionary.GetEnumerator() + => new Enumerator(GetOrCreateMutableDictionary(), Enumerator.ReturnType.DictionaryEntry); + + void IDictionary.Remove(object key) + => ((IDictionary)GetOrCreateMutableDictionary()).Remove(key); + + readonly void ICollection.CopyTo(Array array, int index) + => ((ICollection)ReadOnlyDictionary).CopyTo(array, index); + } + } +} diff --git a/src/Dependencies/Collections/ImmutableSegmentedDictionary`2.cs b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2.cs index 99d8a6ce511d7..bdcdcdddcf96a 100644 --- a/src/Dependencies/Collections/ImmutableSegmentedDictionary`2.cs +++ b/src/Dependencies/Collections/ImmutableSegmentedDictionary`2.cs @@ -147,9 +147,9 @@ public ImmutableSegmentedDictionary Add(TKey key, TValue value) if (self.Contains(new KeyValuePair(key, value))) return self; - var dictionary = new SegmentedDictionary(self._dictionary, self._dictionary.Comparer); - dictionary.Add(key, value); - return new ImmutableSegmentedDictionary(dictionary); + var builder = ToValueBuilder(); + builder.Add(key, value); + return builder.ToImmutable(); } public ImmutableSegmentedDictionary AddRange(IEnumerable> pairs) @@ -162,21 +162,9 @@ public ImmutableSegmentedDictionary AddRange(IEnumerable? dictionary = null; - foreach (var pair in pairs) - { - ICollection> collectionToCheck = dictionary ?? self._dictionary; - if (collectionToCheck.Contains(pair)) - continue; - - dictionary ??= new SegmentedDictionary(self._dictionary, self._dictionary.Comparer); - dictionary.Add(pair.Key, pair.Value); - } - - if (dictionary is null) - return self; - - return new ImmutableSegmentedDictionary(dictionary); + var builder = ToValueBuilder(); + builder.AddRange(pairs); + return builder.ToImmutable(); } public ImmutableSegmentedDictionary Clear() @@ -211,9 +199,9 @@ public ImmutableSegmentedDictionary Remove(TKey key) if (!self._dictionary.ContainsKey(key)) return self; - var dictionary = new SegmentedDictionary(self._dictionary, self._dictionary.Comparer); - dictionary.Remove(key); - return new ImmutableSegmentedDictionary(dictionary); + var builder = ToValueBuilder(); + builder.Remove(key); + return builder.ToImmutable(); } public ImmutableSegmentedDictionary RemoveRange(IEnumerable keys) @@ -221,7 +209,7 @@ public ImmutableSegmentedDictionary RemoveRange(IEnumerable if (keys is null) throw new ArgumentNullException(nameof(keys)); - var result = ToBuilder(); + var result = ToValueBuilder(); result.RemoveRange(keys); return result.ToImmutable(); } @@ -234,9 +222,9 @@ public ImmutableSegmentedDictionary SetItem(TKey key, TValue value return self; } - var dictionary = new SegmentedDictionary(self._dictionary, self._dictionary.Comparer); - dictionary[key] = value; - return new ImmutableSegmentedDictionary(dictionary); + var builder = ToValueBuilder(); + builder[key] = value; + return builder.ToImmutable(); } public ImmutableSegmentedDictionary SetItems(IEnumerable> items) @@ -244,7 +232,7 @@ public ImmutableSegmentedDictionary SetItems(IEnumerable WithComparer(IEqualityComparer public Builder ToBuilder() => new(this); + private ValueBuilder ToValueBuilder() + => new(this); + public override int GetHashCode() => _dictionary?.GetHashCode() ?? 0; diff --git a/src/Dependencies/Collections/ImmutableSegmentedHashSet`1+Builder.cs b/src/Dependencies/Collections/ImmutableSegmentedHashSet`1+Builder.cs index 646c2a4bb93ca..f5140e38238f5 100644 --- a/src/Dependencies/Collections/ImmutableSegmentedHashSet`1+Builder.cs +++ b/src/Dependencies/Collections/ImmutableSegmentedHashSet`1+Builder.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; @@ -15,263 +14,115 @@ internal readonly partial struct ImmutableSegmentedHashSet public sealed class Builder : ISet, IReadOnlyCollection { /// - /// The immutable collection this builder is based on. + /// The private builder implementation. /// - private ImmutableSegmentedHashSet _set; - - /// - /// The current mutable collection this builder is operating on. This field is initialized to a copy of - /// the first time a change is made. - /// - private SegmentedHashSet? _mutableSet; + private ValueBuilder _builder; internal Builder(ImmutableSegmentedHashSet set) - { - _set = set; - _mutableSet = null; - } + => _builder = new ValueBuilder(set); /// public IEqualityComparer KeyComparer { - get - { - return ReadOnlySet.Comparer; - } - - set - { - if (Equals(KeyComparer, value ?? EqualityComparer.Default)) - return; - - _mutableSet = new SegmentedHashSet(ReadOnlySet, value ?? EqualityComparer.Default); - _set = default; - } + get => _builder.KeyComparer; + set => _builder.KeyComparer = value; } /// - public int Count => ReadOnlySet.Count; - - private SegmentedHashSet ReadOnlySet => _mutableSet ?? _set._set; - - bool ICollection.IsReadOnly => false; - - private SegmentedHashSet GetOrCreateMutableSet() - { - if (_mutableSet is null) - { - var originalSet = RoslynImmutableInterlocked.InterlockedExchange(ref _set, default); - if (originalSet.IsDefault) - throw new InvalidOperationException($"Unexpected concurrent access to {GetType()}"); - - _mutableSet = new SegmentedHashSet(originalSet._set, originalSet.KeyComparer); - } + public int Count => _builder.Count; - return _mutableSet; - } + bool ICollection.IsReadOnly => ICollectionCalls.IsReadOnly(ref _builder); /// public bool Add(T item) - { - if (_mutableSet is null && Contains(item)) - return false; - - return GetOrCreateMutableSet().Add(item); - } + => _builder.Add(item); /// public void Clear() - { - if (ReadOnlySet.Count != 0) - { - if (_mutableSet is null) - { - _mutableSet = new SegmentedHashSet(KeyComparer); - _set = default; - } - else - { - _mutableSet.Clear(); - } - } - } + => _builder.Clear(); /// public bool Contains(T item) - => ReadOnlySet.Contains(item); + => _builder.Contains(item); /// public void ExceptWith(IEnumerable other) { - if (other is null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.other); - - if (_mutableSet is not null) - { - _mutableSet.ExceptWith(other); - return; - } - if (other == this) { - Clear(); - return; - } - else if (other is ImmutableSegmentedHashSet otherSet) - { - if (otherSet == _set) - { - Clear(); - return; - } - else if (otherSet.IsEmpty) - { - // No action required - return; - } - else - { - GetOrCreateMutableSet().ExceptWith(otherSet._set); - return; - } + // The ValueBuilder knows how to optimize for this case by calling Clear, provided it does not need + // to access properties of the wrapping Builder instance. + _builder.ExceptWith(_builder.ReadOnlySet); } else { - // Manually enumerate to avoid changes to the builder if 'other' is empty or does not contain any - // items present in the current set. - SegmentedHashSet? mutableSet = null; - foreach (var item in other) - { - if (mutableSet is null) - { - if (!ReadOnlySet.Contains(item)) - continue; - - mutableSet = GetOrCreateMutableSet(); - } - - mutableSet.Remove(item); - } - - return; + _builder.ExceptWith(other); } } /// public Enumerator GetEnumerator() - => new Enumerator(GetOrCreateMutableSet()); + => _builder.GetEnumerator(); /// public void IntersectWith(IEnumerable other) - => GetOrCreateMutableSet().IntersectWith(other); + => _builder.IntersectWith(other); /// public bool IsProperSubsetOf(IEnumerable other) - => ReadOnlySet.IsProperSubsetOf(other); + => _builder.IsProperSubsetOf(other); /// public bool IsProperSupersetOf(IEnumerable other) - => ReadOnlySet.IsProperSupersetOf(other); + => _builder.IsProperSupersetOf(other); /// public bool IsSubsetOf(IEnumerable other) - => ReadOnlySet.IsSubsetOf(other); + => _builder.IsSubsetOf(other); /// public bool IsSupersetOf(IEnumerable other) - => ReadOnlySet.IsSupersetOf(other); + => _builder.IsSupersetOf(other); /// public bool Overlaps(IEnumerable other) - => ReadOnlySet.Overlaps(other); + => _builder.Overlaps(other); /// public bool Remove(T item) - { - if (_mutableSet is null && !Contains(item)) - return false; - - return GetOrCreateMutableSet().Remove(item); - } + => _builder.Remove(item); /// public bool SetEquals(IEnumerable other) - => ReadOnlySet.SetEquals(other); + => _builder.SetEquals(other); /// public void SymmetricExceptWith(IEnumerable other) - => GetOrCreateMutableSet().SymmetricExceptWith(other); + => _builder.SymmetricExceptWith(other); /// public bool TryGetValue(T equalValue, out T actualValue) - { - if (ReadOnlySet.TryGetValue(equalValue, out var value)) - { - actualValue = value; - return true; - } - - actualValue = equalValue; - return false; - } + => _builder.TryGetValue(equalValue, out actualValue); /// public void UnionWith(IEnumerable other) - { - if (other is null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.other); - - if (_mutableSet is not null) - { - _mutableSet.UnionWith(other); - return; - } - - if (other is ImmutableSegmentedHashSet { IsEmpty: true }) - { - return; - } - else - { - // Manually enumerate to avoid changes to the builder if 'other' is empty or only contains items - // already present in the current set. - SegmentedHashSet? mutableSet = null; - foreach (var item in other) - { - if (mutableSet is null) - { - if (ReadOnlySet.Contains(item)) - continue; - - mutableSet = GetOrCreateMutableSet(); - } - - mutableSet.Add(item); - } - - return; - } - } + => _builder.UnionWith(other); /// public ImmutableSegmentedHashSet ToImmutable() - { - _set = new ImmutableSegmentedHashSet(ReadOnlySet); - _mutableSet = null; - return _set; - } + => _builder.ToImmutable(); void ICollection.Add(T item) - => ((ICollection)GetOrCreateMutableSet()).Add(item); + => ICollectionCalls.Add(ref _builder, item); void ICollection.CopyTo(T[] array, int arrayIndex) - => ((ICollection)ReadOnlySet).CopyTo(array, arrayIndex); + => ICollectionCalls.CopyTo(ref _builder, array, arrayIndex); IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); + => IEnumerableCalls.GetEnumerator(ref _builder); IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); + => IEnumerableCalls.GetEnumerator(ref _builder); } } } diff --git a/src/Dependencies/Collections/ImmutableSegmentedHashSet`1+ValueBuilder.cs b/src/Dependencies/Collections/ImmutableSegmentedHashSet`1+ValueBuilder.cs new file mode 100644 index 0000000000000..c672df7acd97f --- /dev/null +++ b/src/Dependencies/Collections/ImmutableSegmentedHashSet`1+ValueBuilder.cs @@ -0,0 +1,282 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using Microsoft.CodeAnalysis.Collections.Internal; + +namespace Microsoft.CodeAnalysis.Collections +{ + internal readonly partial struct ImmutableSegmentedHashSet + { + private struct ValueBuilder : ISet, IReadOnlyCollection + { + /// + /// The immutable collection this builder is based on. + /// + private ImmutableSegmentedHashSet _set; + + /// + /// The current mutable collection this builder is operating on. This field is initialized to a copy of + /// the first time a change is made. + /// + private SegmentedHashSet? _mutableSet; + + internal ValueBuilder(ImmutableSegmentedHashSet set) + { + _set = set; + _mutableSet = null; + } + + /// + public IEqualityComparer KeyComparer + { + readonly get + { + return ReadOnlySet.Comparer; + } + + set + { + if (Equals(KeyComparer, value ?? EqualityComparer.Default)) + return; + + _mutableSet = new SegmentedHashSet(ReadOnlySet, value ?? EqualityComparer.Default); + _set = default; + } + } + + /// + public readonly int Count => ReadOnlySet.Count; + + internal readonly SegmentedHashSet ReadOnlySet => _mutableSet ?? _set._set; + + readonly bool ICollection.IsReadOnly => false; + + private SegmentedHashSet GetOrCreateMutableSet() + { + if (_mutableSet is null) + { + var originalSet = RoslynImmutableInterlocked.InterlockedExchange(ref _set, default); + if (originalSet.IsDefault) + throw new InvalidOperationException($"Unexpected concurrent access to {GetType()}"); + + _mutableSet = new SegmentedHashSet(originalSet._set, originalSet.KeyComparer); + } + + return _mutableSet; + } + + /// + public bool Add(T item) + { + if (_mutableSet is null && Contains(item)) + return false; + + return GetOrCreateMutableSet().Add(item); + } + + /// + public void Clear() + { + if (ReadOnlySet.Count != 0) + { + if (_mutableSet is null) + { + _mutableSet = new SegmentedHashSet(KeyComparer); + _set = default; + } + else + { + _mutableSet.Clear(); + } + } + } + + /// + public readonly bool Contains(T item) + => ReadOnlySet.Contains(item); + + /// + public void ExceptWith(IEnumerable other) + { + if (other is null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.other); + + if (_mutableSet is not null) + { + _mutableSet.ExceptWith(other); + return; + } + + // ValueBuilder is not a public API, so there shouldn't be any callers trying to pass a boxed instance + // to this method. + Debug.Assert(other is not ValueBuilder); + + if (other == ReadOnlySet) + { + Clear(); + return; + } + else if (other is ImmutableSegmentedHashSet otherSet) + { + if (otherSet == _set) + { + Clear(); + return; + } + else if (otherSet.IsEmpty) + { + // No action required + return; + } + else + { + GetOrCreateMutableSet().ExceptWith(otherSet._set); + return; + } + } + else + { + // Manually enumerate to avoid changes to the builder if 'other' is empty or does not contain any + // items present in the current set. + SegmentedHashSet? mutableSet = null; + foreach (var item in other) + { + if (mutableSet is null) + { + if (!ReadOnlySet.Contains(item)) + continue; + + mutableSet = GetOrCreateMutableSet(); + } + + mutableSet.Remove(item); + } + + return; + } + } + + /// + public Enumerator GetEnumerator() + => new(GetOrCreateMutableSet()); + + /// + public void IntersectWith(IEnumerable other) + => GetOrCreateMutableSet().IntersectWith(other); + + /// + public readonly bool IsProperSubsetOf(IEnumerable other) + => ReadOnlySet.IsProperSubsetOf(other); + + /// + public readonly bool IsProperSupersetOf(IEnumerable other) + => ReadOnlySet.IsProperSupersetOf(other); + + /// + public readonly bool IsSubsetOf(IEnumerable other) + => ReadOnlySet.IsSubsetOf(other); + + /// + public readonly bool IsSupersetOf(IEnumerable other) + => ReadOnlySet.IsSupersetOf(other); + + /// + public readonly bool Overlaps(IEnumerable other) + => ReadOnlySet.Overlaps(other); + + /// + public bool Remove(T item) + { + if (_mutableSet is null && !Contains(item)) + return false; + + return GetOrCreateMutableSet().Remove(item); + } + + /// + public readonly bool SetEquals(IEnumerable other) + => ReadOnlySet.SetEquals(other); + + /// + public void SymmetricExceptWith(IEnumerable other) + => GetOrCreateMutableSet().SymmetricExceptWith(other); + + /// + public readonly bool TryGetValue(T equalValue, out T actualValue) + { + if (ReadOnlySet.TryGetValue(equalValue, out var value)) + { + actualValue = value; + return true; + } + + actualValue = equalValue; + return false; + } + + /// + public void UnionWith(IEnumerable other) + { + if (other is null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.other); + + if (_mutableSet is not null) + { + _mutableSet.UnionWith(other); + return; + } + + if (other is ImmutableSegmentedHashSet { IsEmpty: true }) + { + return; + } + else + { + // Manually enumerate to avoid changes to the builder if 'other' is empty or only contains items + // already present in the current set. + SegmentedHashSet? mutableSet = null; + foreach (var item in other) + { + if (mutableSet is null) + { + if (ReadOnlySet.Contains(item)) + continue; + + mutableSet = GetOrCreateMutableSet(); + } + + mutableSet.Add(item); + } + + return; + } + } + + /// + public ImmutableSegmentedHashSet ToImmutable() + { + _set = new ImmutableSegmentedHashSet(ReadOnlySet); + _mutableSet = null; + return _set; + } + + void ICollection.Add(T item) + => ((ICollection)GetOrCreateMutableSet()).Add(item); + + readonly void ICollection.CopyTo(T[] array, int arrayIndex) + => ((ICollection)ReadOnlySet).CopyTo(array, arrayIndex); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + } + } +} diff --git a/src/Dependencies/Collections/ImmutableSegmentedHashSet`1.cs b/src/Dependencies/Collections/ImmutableSegmentedHashSet`1.cs index a00875ab0a459..b99e4e99373cc 100644 --- a/src/Dependencies/Collections/ImmutableSegmentedHashSet`1.cs +++ b/src/Dependencies/Collections/ImmutableSegmentedHashSet`1.cs @@ -10,6 +10,57 @@ namespace Microsoft.CodeAnalysis.Collections { + /// + /// Represents a segmented hash set that is immutable; meaning it cannot be changed once it is created. + /// + /// + /// There are different scenarios best for and others + /// best for . + /// + /// The following table summarizes the performance characteristics of + /// : + /// + /// + /// + /// Operation + /// Complexity + /// Complexity + /// Comments + /// + /// + /// Contains + /// O(1) + /// O(log n) + /// Directly index into the underlying segmented list + /// + /// + /// Add() + /// O(n) + /// O(log n) + /// Requires creating a new segmented hash set and cloning all impacted segments + /// + /// + /// + /// This type is backed by segmented arrays to avoid using the Large Object Heap without impacting algorithmic + /// complexity. + /// + /// The type of the value in the set. + /// + /// This type has a documented contract of being exactly one reference-type field in size. Our own + /// class depends on it, as well as others externally. + /// + /// IMPORTANT NOTICE FOR MAINTAINERS AND REVIEWERS: + /// + /// This type should be thread-safe. As a struct, it cannot protect its own fields from being changed from one + /// thread while its members are executing on other threads because structs can change in place simply by + /// reassigning the field containing this struct. Therefore it is extremely important that ⚠⚠ Every member + /// should only dereference this ONCE ⚠⚠. If a member needs to reference the + /// field, that counts as a dereference of this. Calling other instance members + /// (properties or methods) also counts as dereferencing this. Any member that needs to use this more + /// than once must instead assign this to a local variable and use that for the rest of the code instead. + /// This effectively copies the one field in the struct to a local variable so that it is insulated from other + /// threads. + /// internal readonly partial struct ImmutableSegmentedHashSet : IImmutableSet, ISet, ICollection, IEquatable> { /// @@ -67,9 +118,8 @@ public ImmutableSegmentedHashSet Add(T value) } else { - // TODO: Avoid the builder allocation // TODO: Reuse all pages with no changes - var builder = self.ToBuilder(); + var builder = self.ToValueBuilder(); builder.Add(value); return builder.ToImmutable(); } @@ -113,9 +163,8 @@ public ImmutableSegmentedHashSet Except(IEnumerable other) } else { - // TODO: Avoid the builder allocation // TODO: Reuse all pages with no changes - var builder = self.ToBuilder(); + var builder = self.ToValueBuilder(); builder.ExceptWith(other); return builder.ToImmutable(); } @@ -136,9 +185,8 @@ public ImmutableSegmentedHashSet Intersect(IEnumerable other) } else { - // TODO: Avoid the builder allocation // TODO: Reuse all pages with no changes - var builder = self.ToBuilder(); + var builder = self.ToValueBuilder(); builder.IntersectWith(other); return builder.ToImmutable(); } @@ -175,9 +223,8 @@ public ImmutableSegmentedHashSet Remove(T value) } else { - // TODO: Avoid the builder allocation // TODO: Reuse all pages with no changes - var builder = self.ToBuilder(); + var builder = self.ToValueBuilder(); builder.Remove(value); return builder.ToImmutable(); } @@ -206,9 +253,8 @@ public ImmutableSegmentedHashSet SymmetricExcept(IEnumerable other) } else { - // TODO: Avoid the builder allocation // TODO: Reuse all pages with no changes - var builder = self.ToBuilder(); + var builder = self.ToValueBuilder(); builder.SymmetricExceptWith(other); return builder.ToImmutable(); } @@ -240,9 +286,8 @@ public ImmutableSegmentedHashSet Union(IEnumerable other) return otherSet.WithComparer(self.KeyComparer); } - // TODO: Avoid the builder allocation // TODO: Reuse all pages with no changes - var builder = self.ToBuilder(); + var builder = self.ToValueBuilder(); builder.UnionWith(other); return builder.ToImmutable(); } @@ -251,6 +296,9 @@ public ImmutableSegmentedHashSet Union(IEnumerable other) public Builder ToBuilder() => new(this); + private ValueBuilder ToValueBuilder() + => new ValueBuilder(this); + /// public ImmutableSegmentedHashSet WithComparer(IEqualityComparer? equalityComparer) { diff --git a/src/Dependencies/Collections/ImmutableSegmentedList`1+Builder.cs b/src/Dependencies/Collections/ImmutableSegmentedList`1+Builder.cs index 865107bbe052e..174d53ff8904b 100644 --- a/src/Dependencies/Collections/ImmutableSegmentedList`1+Builder.cs +++ b/src/Dependencies/Collections/ImmutableSegmentedList`1+Builder.cs @@ -14,7 +14,7 @@ internal partial struct ImmutableSegmentedList public sealed class Builder : IList, IReadOnlyList, IList { /// - /// The immutable collection this builder is based on. + /// The private builder implementation. /// private ValueBuilder _builder; diff --git a/src/Dependencies/Collections/Internal/ICollectionCalls`1.cs b/src/Dependencies/Collections/Internal/ICollectionCalls`1.cs index 3eec844a90f5b..45fa0142859da 100644 --- a/src/Dependencies/Collections/Internal/ICollectionCalls`1.cs +++ b/src/Dependencies/Collections/Internal/ICollectionCalls`1.cs @@ -20,5 +20,13 @@ internal static class ICollectionCalls public static bool IsReadOnly(ref TCollection collection) where TCollection : ICollection => collection.IsReadOnly; + + public static void Add(ref TCollection collection, T item) + where TCollection : ICollection + => collection.Add(item); + + public static void CopyTo(ref TCollection collection, T[] array, int arrayIndex) + where TCollection : ICollection + => collection.CopyTo(array, arrayIndex); } } diff --git a/src/Dependencies/Collections/Internal/IDictionaryCalls.cs b/src/Dependencies/Collections/Internal/IDictionaryCalls.cs new file mode 100644 index 0000000000000..79d95fd22ef6e --- /dev/null +++ b/src/Dependencies/Collections/Internal/IDictionaryCalls.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; + +namespace Microsoft.CodeAnalysis.Collections.Internal +{ + /// + /// Provides static methods to invoke members on value types that explicitly implement the + /// member. + /// + /// + /// Normally, invocation of explicit interface members requires boxing or copying the value type, which is + /// especially problematic for operations that mutate the value. Invocation through these helpers behaves like a + /// normal call to an implicitly implemented member. + /// + internal static class IDictionaryCalls + { + public static bool IsFixedSize(ref TDictionary dictionary) + where TDictionary : IDictionary + => dictionary.IsFixedSize; + + public static bool IsReadOnly(ref TDictionary dictionary) + where TDictionary : IDictionary + => dictionary.IsReadOnly; + + public static object? GetItem(ref TDictionary dictionary, object key) + where TDictionary : IDictionary + => dictionary[key]; + + public static void SetItem(ref TDictionary dictionary, object key, object? value) + where TDictionary : IDictionary + => dictionary[key] = value; + + public static void Add(ref TDictionary dictionary, object key, object? value) + where TDictionary : IDictionary + => dictionary.Add(key, value); + + public static bool Contains(ref TDictionary dictionary, object key) + where TDictionary : IDictionary + => dictionary.Contains(key); + + public static void CopyTo(ref TDictionary dictionary, Array array, int index) + where TDictionary : IDictionary + => dictionary.CopyTo(array, index); + + public static IDictionaryEnumerator GetEnumerator(ref TDictionary dictionary) + where TDictionary : IDictionary + => dictionary.GetEnumerator(); + + public static void Remove(ref TDictionary dictionary, object key) + where TDictionary : IDictionary + => dictionary.Remove(key); + } +} diff --git a/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.projitems b/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.projitems index 793bff9426dc8..e17523e5a7ffd 100644 --- a/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.projitems +++ b/src/Dependencies/Collections/Microsoft.CodeAnalysis.Collections.projitems @@ -19,6 +19,7 @@ + @@ -26,6 +27,7 @@ + @@ -40,6 +42,7 @@ +