File: Utilities\ValueSetFactory.EnumeratedValueSet.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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.Immutable;
using System.Diagnostics;
using System.Linq;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal static partial class ValueSetFactory
    {
        /// <summary>
        /// A value set that only supports equality and works by including or excluding specific values.
        /// This is used for value set of <see cref="System.String"/> because the language defines no
        /// relational operators for it; such a set can be formed only by including explicitly mentioned
        /// members (or the inverse, excluding them, by complementing the set).
        /// </summary>
        private sealed class EnumeratedValueSet<T> : IValueSet<T>
            where T : notnull
        {
            /// <summary>
            /// In <see cref="_included"/>, then members are listed by inclusion.  Otherwise all members
            /// are assumed to be contained in the set unless excluded.
            /// </summary>
            private readonly bool _included;
 
            private readonly ImmutableHashSet<T> _membersIncludedOrExcluded;
 
            private readonly IEquatableValueTC<T> _tc;
 
            private EnumeratedValueSet(bool included, ImmutableHashSet<T> membersIncludedOrExcluded, IEquatableValueTC<T> tc) =>
                (this._included, this._membersIncludedOrExcluded, this._tc) = (included, membersIncludedOrExcluded, tc);
 
            public static EnumeratedValueSet<T> AllValues(IEquatableValueTC<T> tc)
                => new EnumeratedValueSet<T>(included: false, ImmutableHashSet<T>.Empty, tc);
 
            public static EnumeratedValueSet<T> NoValues(IEquatableValueTC<T> tc)
                => new EnumeratedValueSet<T>(included: true, ImmutableHashSet<T>.Empty, tc);
 
            internal static EnumeratedValueSet<T> Including(T value, IEquatableValueTC<T> tc)
                => new EnumeratedValueSet<T>(included: true, ImmutableHashSet<T>.Empty.Add(value), tc);
 
            public bool IsEmpty => _included && _membersIncludedOrExcluded.IsEmpty;
 
            ConstantValue IValueSet.Sample
            {
                get
                {
                    if (IsEmpty) throw new ArgumentException();
                    if (_included)
                        return _tc.ToConstantValue(_membersIncludedOrExcluded.OrderBy(k => k).First());
                    if (typeof(T) == typeof(string))
                    {
                        // try some simple strings.
                        if (this.Any(BinaryOperatorKind.Equal, (T)(object)""))
                            return _tc.ToConstantValue((T)(object)"");
                        for (char c = 'A'; c <= 'z'; c++)
                            if (this.Any(BinaryOperatorKind.Equal, (T)(object)c.ToString()))
                                return _tc.ToConstantValue((T)(object)c.ToString());
                    }
                    // If that doesn't work, choose from a sufficiently large random selection of values.
                    // Since this is an excluded set, they cannot all be excluded
                    var candidates = _tc.RandomValues(_membersIncludedOrExcluded.Count + 1, new Random(0), _membersIncludedOrExcluded.Count + 1);
                    foreach (var value in candidates)
                    {
                        if (this.Any(BinaryOperatorKind.Equal, value))
                            return _tc.ToConstantValue(value);
                    }
 
                    throw ExceptionUtilities.Unreachable();
                }
            }
 
            public bool Any(BinaryOperatorKind relation, T value)
            {
                switch (relation)
                {
                    case BinaryOperatorKind.Equal:
                        return _included == _membersIncludedOrExcluded.Contains(value);
                    default:
                        return true; // supported for error recovery
                }
            }
 
            bool IValueSet.Any(BinaryOperatorKind relation, ConstantValue value) => value.IsBad || Any(relation, _tc.FromConstantValue(value));
 
            public bool All(BinaryOperatorKind relation, T value)
            {
                switch (relation)
                {
                    case BinaryOperatorKind.Equal:
                        if (!_included)
                            return false;
                        switch (_membersIncludedOrExcluded.Count)
                        {
                            case 0:
                                return true;
                            case 1:
                                return _membersIncludedOrExcluded.Contains(value);
                            default:
                                return false;
                        }
                    default:
                        return false; // supported for error recovery
                }
            }
 
            bool IValueSet.All(BinaryOperatorKind relation, ConstantValue value) => !value.IsBad && All(relation, _tc.FromConstantValue(value));
 
            public IValueSet<T> Complement() => new EnumeratedValueSet<T>(!_included, _membersIncludedOrExcluded, _tc);
 
            IValueSet IValueSet.Complement() => this.Complement();
 
            public IValueSet<T> Intersect(IValueSet<T> o)
            {
                if (this == o)
                    return this;
                var other = (EnumeratedValueSet<T>)o;
                Debug.Assert(object.ReferenceEquals(this._tc, other._tc));
 
                var (larger, smaller) = (this._membersIncludedOrExcluded.Count > other._membersIncludedOrExcluded.Count) ? (this, other) : (other, this);
                switch (larger._included, smaller._included)
                {
                    case (true, true):
                        return new EnumeratedValueSet<T>(true, larger._membersIncludedOrExcluded.Intersect(smaller._membersIncludedOrExcluded), _tc);
                    case (true, false):
                        return new EnumeratedValueSet<T>(true, larger._membersIncludedOrExcluded.Except(smaller._membersIncludedOrExcluded), _tc);
                    case (false, false):
                        return new EnumeratedValueSet<T>(false, larger._membersIncludedOrExcluded.Union(smaller._membersIncludedOrExcluded), _tc);
                    case (false, true):
                        return new EnumeratedValueSet<T>(true, smaller._membersIncludedOrExcluded.Except(larger._membersIncludedOrExcluded), _tc);
                }
            }
 
            IValueSet IValueSet.Intersect(IValueSet other) => Intersect((IValueSet<T>)other);
 
            public IValueSet<T> Union(IValueSet<T> o)
            {
                if (this == o)
                    return this;
                var other = (EnumeratedValueSet<T>)o;
                Debug.Assert(object.ReferenceEquals(this._tc, other._tc));
 
                var (larger, smaller) = (this._membersIncludedOrExcluded.Count > other._membersIncludedOrExcluded.Count) ? (this, other) : (other, this);
                switch (larger._included, smaller._included)
                {
                    case (false, false):
                        return new EnumeratedValueSet<T>(false, larger._membersIncludedOrExcluded.Intersect(smaller._membersIncludedOrExcluded), _tc);
                    case (false, true):
                        return new EnumeratedValueSet<T>(false, larger._membersIncludedOrExcluded.Except(smaller._membersIncludedOrExcluded), _tc);
                    case (true, true):
                        return new EnumeratedValueSet<T>(true, larger._membersIncludedOrExcluded.Union(smaller._membersIncludedOrExcluded), _tc);
                    case (true, false):
                        return new EnumeratedValueSet<T>(false, smaller._membersIncludedOrExcluded.Except(larger._membersIncludedOrExcluded), _tc);
                }
            }
 
            IValueSet IValueSet.Union(IValueSet other) => Union((IValueSet<T>)other);
 
            public override bool Equals(object? obj)
            {
                if (obj is not EnumeratedValueSet<T> other)
                    return false;
 
                Debug.Assert(object.ReferenceEquals(this._tc, other._tc));
                return this._included == other._included
                    && this._membersIncludedOrExcluded.SetEqualsWithoutIntermediateHashSet(other._membersIncludedOrExcluded);
            }
 
            public override int GetHashCode() => Hash.Combine(this._included.GetHashCode(), this._membersIncludedOrExcluded.GetHashCode());
 
            public override string ToString() => $"{(this._included ? "" : "~")}{{{string.Join(",", _membersIncludedOrExcluded.Select(o => o.ToString()))}{"}"}";
        }
    }
}