// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; // This is needed due to NativeAOT which doesn't enable nullable globally yet #nullable enable namespace ILLink.Shared.DataFlow { // This is a dictionary along with a default value, where every possible key either maps to // the default value, or another value. The default value is never explicitly stored in the dictionary, // and the empty dictionary (where all possible keys have the default value) is represented without // actually allocating a dictionary. public struct DefaultValueDictionary<TKey, TValue> : IEquatable<DefaultValueDictionary<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>> where TKey : IEquatable<TKey> where TValue : IEquatable<TValue> { private Dictionary<TKey, TValue>? Dictionary; private readonly TValue DefaultValue; public DefaultValueDictionary(TValue defaultValue) => (Dictionary, DefaultValue) = (null, defaultValue); private DefaultValueDictionary(TValue defaultValue, Dictionary<TKey, TValue> dictionary) => (Dictionary, DefaultValue) = (dictionary, defaultValue); public DefaultValueDictionary(DefaultValueDictionary<TKey, TValue> other) { Dictionary = other.Dictionary == null ? null : new Dictionary<TKey, TValue>(other.Dictionary); DefaultValue = other.DefaultValue; } public TValue Get(TKey key) => Dictionary?.TryGetValue(key, out var value) == true ? value : DefaultValue; public void Set(TKey key, TValue value) { if (value.Equals(DefaultValue)) Dictionary?.Remove(key); else (Dictionary ??= new Dictionary<TKey, TValue>())[key] = value; } public bool Equals(DefaultValueDictionary<TKey, TValue> other) { if (!DefaultValue.Equals(other.DefaultValue)) return false; if (Dictionary == null) return other.Dictionary == null; if (other.Dictionary == null) return false; if (Dictionary.Count != other.Dictionary.Count) return false; foreach (var kvp in other.Dictionary) { if (!Get(kvp.Key).Equals(kvp.Value)) return false; } return true; } public override bool Equals(object? obj) => obj is DefaultValueDictionary<TKey, TValue> other && Equals(other); public int Count => Dictionary?.Count ?? 0; public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return Dictionary?.GetEnumerator() ?? Enumerable.Empty<KeyValuePair<TKey, TValue>>().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public override string ToString() { StringBuilder sb = new(); sb.Append('{'); if (Dictionary != null) { foreach (var kvp in Dictionary) sb.AppendLine().Append('\t').Append(kvp.Key.ToString()).Append(" -> ").Append(kvp.Value.ToString()); } sb.AppendLine().Append("\t_ -> ").Append(DefaultValue.ToString()); sb.AppendLine().Append('}'); return sb.ToString(); } public DefaultValueDictionary<TKey, TValue> Clone() { var defaultValue = DefaultValue is IDeepCopyValue<TValue> copyDefaultValue ? copyDefaultValue.DeepCopy() : DefaultValue; if (Dictionary == null) return new DefaultValueDictionary<TKey, TValue>(defaultValue); var dict = new Dictionary<TKey, TValue>(); foreach (var kvp in Dictionary) { var key = kvp.Key; var value = kvp.Value; dict.Add(key, value is IDeepCopyValue<TValue> copyValue ? copyValue.DeepCopy() : value); } return new DefaultValueDictionary<TKey, TValue>(defaultValue, dict); } // Prevent warning CS0659 https://learn.microsoft.com/dotnet/csharp/misc/cs0659. // This type should never be used as a dictionary key. public override int GetHashCode() => throw new NotImplementedException(); public static bool operator ==(DefaultValueDictionary<TKey, TValue> left, DefaultValueDictionary<TKey, TValue> right) => left.Equals(right); public static bool operator !=(DefaultValueDictionary<TKey, TValue> left, DefaultValueDictionary<TKey, TValue> right) => !(left == right); } }