diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ByteTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ByteTC.cs index 0eb729645c44c..c71c4139e09fc 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ByteTC.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ByteTC.cs @@ -11,8 +11,10 @@ namespace Microsoft.CodeAnalysis.CSharp internal static partial class ValueSetFactory { - private struct ByteTC : INumericTC + private class ByteTC : INumericTC { + public static readonly ByteTC Instance = new ByteTC(); + byte INumericTC.MinValue => byte.MinValue; byte INumericTC.MaxValue => byte.MaxValue; diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.CharTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.CharTC.cs index a0354af08fea0..8320ce5e6d27a 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.CharTC.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.CharTC.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics; -using System.Globalization; namespace Microsoft.CodeAnalysis.CSharp { @@ -12,8 +11,10 @@ namespace Microsoft.CodeAnalysis.CSharp internal static partial class ValueSetFactory { - private struct CharTC : INumericTC + private class CharTC : INumericTC { + public static readonly CharTC Instance = new CharTC(); + char INumericTC.MinValue => char.MinValue; char INumericTC.MaxValue => char.MaxValue; diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalTC.cs index 51a2040f392c9..d11c0dcb63afc 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalTC.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalTC.cs @@ -12,8 +12,10 @@ namespace Microsoft.CodeAnalysis.CSharp internal static partial class ValueSetFactory { - private struct DecimalTC : INumericTC + private class DecimalTC : INumericTC { + public static readonly DecimalTC Instance = new DecimalTC(); + // These are the smallest nonzero normal mantissa value (in three parts) below which you could use a higher scale. // This is the 96-bit representation of ((2^96)-1) / 10; private const uint transitionLow = 0x99999999; @@ -112,7 +114,7 @@ decimal INumericTC.Prev(decimal value) public decimal Random(Random random) { - INumericTC uinttc = default(UIntTC); + INumericTC uinttc = UIntTC.Instance; return new DecimalRep( low: uinttc.Random(random), mid: uinttc.Random(random), diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalValueSetFactory.cs index cd1e60dc3320d..7de64cb191c54 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalValueSetFactory.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DecimalValueSetFactory.cs @@ -12,20 +12,20 @@ private sealed class DecimalValueSetFactory : IValueSetFactory, IValueS { public static readonly DecimalValueSetFactory Instance = new DecimalValueSetFactory(); - private readonly IValueSetFactory _underlying = NumericValueSetFactory.Instance; + private readonly IValueSetFactory _underlying = new NumericValueSetFactory(DecimalTC.Instance); - IValueSet IValueSetFactory.AllValues => NumericValueSet.AllValues; + IValueSet IValueSetFactory.AllValues => NumericValueSet.AllValues(DecimalTC.Instance); - IValueSet IValueSetFactory.NoValues => NumericValueSet.NoValues; + IValueSet IValueSetFactory.NoValues => NumericValueSet.NoValues(DecimalTC.Instance); public IValueSet Related(BinaryOperatorKind relation, decimal value) => _underlying.Related(relation, DecimalTC.Normalize(value)); IValueSet IValueSetFactory.Random(int expectedSize, Random random) => _underlying.Random(expectedSize, random); - ConstantValue IValueSetFactory.RandomValue(Random random) => ConstantValue.Create(default(DecimalTC).Random(random)); + ConstantValue IValueSetFactory.RandomValue(Random random) => ConstantValue.Create(DecimalTC.Instance.Random(random)); IValueSet IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue value) => - value.IsBad ? NumericValueSet.AllValues : Related(relation, default(DecimalTC).FromConstantValue(value)); + value.IsBad ? NumericValueSet.AllValues(DecimalTC.Instance) : Related(relation, DecimalTC.Instance.FromConstantValue(value)); bool IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue left, ConstantValue right) => _underlying.Related(relation, left, right); } diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DoubleTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DoubleTC.cs index 0143fcd59be3f..e98c3866bc079 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DoubleTC.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.DoubleTC.cs @@ -11,8 +11,10 @@ namespace Microsoft.CodeAnalysis.CSharp internal static partial class ValueSetFactory { - private struct DoubleTC : FloatingTC, INumericTC + private class DoubleTC : FloatingTC, INumericTC { + public static readonly DoubleTC Instance = new DoubleTC(); + double INumericTC.MinValue => double.NegativeInfinity; double INumericTC.MaxValue => double.PositiveInfinity; diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSet.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSet.cs index 1f36ac3ef4928..6e9de4e87dc52 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSet.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSet.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using Roslyn.Utilities; @@ -17,8 +18,7 @@ internal static partial class ValueSetFactory /// 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). /// - private sealed class EnumeratedValueSet : IValueSet - where TTC : struct, IEquatableValueTC + private sealed class EnumeratedValueSet : IValueSet where T : notnull { /// @@ -29,14 +29,19 @@ private sealed class EnumeratedValueSet : IValueSet private readonly ImmutableHashSet _membersIncludedOrExcluded; - private EnumeratedValueSet(bool included, ImmutableHashSet membersIncludedOrExcluded) => - (this._included, this._membersIncludedOrExcluded) = (included, membersIncludedOrExcluded); + private readonly IEquatableValueTC _tc; - public static readonly EnumeratedValueSet AllValues = new EnumeratedValueSet(included: false, ImmutableHashSet.Empty); + private EnumeratedValueSet(bool included, ImmutableHashSet membersIncludedOrExcluded, IEquatableValueTC tc) => + (this._included, this._membersIncludedOrExcluded, this._tc) = (included, membersIncludedOrExcluded, tc); - public static readonly EnumeratedValueSet NoValues = new EnumeratedValueSet(included: true, ImmutableHashSet.Empty); + public static EnumeratedValueSet AllValues(IEquatableValueTC tc) + => new EnumeratedValueSet(included: false, ImmutableHashSet.Empty, tc); - internal static EnumeratedValueSet Including(T value) => new EnumeratedValueSet(included: true, ImmutableHashSet.Empty.Add(value)); + public static EnumeratedValueSet NoValues(IEquatableValueTC tc) + => new EnumeratedValueSet(included: true, ImmutableHashSet.Empty, tc); + + internal static EnumeratedValueSet Including(T value, IEquatableValueTC tc) + => new EnumeratedValueSet(included: true, ImmutableHashSet.Empty.Add(value), tc); public bool IsEmpty => _included && _membersIncludedOrExcluded.IsEmpty; @@ -45,25 +50,24 @@ ConstantValue IValueSet.Sample get { if (IsEmpty) throw new ArgumentException(); - var tc = default(TTC); if (_included) - return tc.ToConstantValue(_membersIncludedOrExcluded.OrderBy(k => k).First()); + 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)""); + 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()); + 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); + 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); + return _tc.ToConstantValue(value); } throw ExceptionUtilities.Unreachable(); @@ -81,7 +85,7 @@ public bool Any(BinaryOperatorKind relation, T value) } } - bool IValueSet.Any(BinaryOperatorKind relation, ConstantValue value) => value.IsBad || Any(relation, default(TTC).FromConstantValue(value)); + bool IValueSet.Any(BinaryOperatorKind relation, ConstantValue value) => value.IsBad || Any(relation, _tc.FromConstantValue(value)); public bool All(BinaryOperatorKind relation, T value) { @@ -104,9 +108,9 @@ public bool All(BinaryOperatorKind relation, T value) } } - bool IValueSet.All(BinaryOperatorKind relation, ConstantValue value) => !value.IsBad && All(relation, default(TTC).FromConstantValue(value)); + bool IValueSet.All(BinaryOperatorKind relation, ConstantValue value) => !value.IsBad && All(relation, _tc.FromConstantValue(value)); - public IValueSet Complement() => new EnumeratedValueSet(!_included, _membersIncludedOrExcluded); + public IValueSet Complement() => new EnumeratedValueSet(!_included, _membersIncludedOrExcluded, _tc); IValueSet IValueSet.Complement() => this.Complement(); @@ -114,18 +118,20 @@ public IValueSet Intersect(IValueSet o) { if (this == o) return this; - var other = (EnumeratedValueSet)o; + var other = (EnumeratedValueSet)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(true, larger._membersIncludedOrExcluded.Intersect(smaller._membersIncludedOrExcluded)); + return new EnumeratedValueSet(true, larger._membersIncludedOrExcluded.Intersect(smaller._membersIncludedOrExcluded), _tc); case (true, false): - return new EnumeratedValueSet(true, larger._membersIncludedOrExcluded.Except(smaller._membersIncludedOrExcluded)); + return new EnumeratedValueSet(true, larger._membersIncludedOrExcluded.Except(smaller._membersIncludedOrExcluded), _tc); case (false, false): - return new EnumeratedValueSet(false, larger._membersIncludedOrExcluded.Union(smaller._membersIncludedOrExcluded)); + return new EnumeratedValueSet(false, larger._membersIncludedOrExcluded.Union(smaller._membersIncludedOrExcluded), _tc); case (false, true): - return new EnumeratedValueSet(true, smaller._membersIncludedOrExcluded.Except(larger._membersIncludedOrExcluded)); + return new EnumeratedValueSet(true, smaller._membersIncludedOrExcluded.Except(larger._membersIncludedOrExcluded), _tc); } } @@ -135,18 +141,20 @@ public IValueSet Union(IValueSet o) { if (this == o) return this; - var other = (EnumeratedValueSet)o; + var other = (EnumeratedValueSet)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(false, larger._membersIncludedOrExcluded.Intersect(smaller._membersIncludedOrExcluded)); + return new EnumeratedValueSet(false, larger._membersIncludedOrExcluded.Intersect(smaller._membersIncludedOrExcluded), _tc); case (false, true): - return new EnumeratedValueSet(false, larger._membersIncludedOrExcluded.Except(smaller._membersIncludedOrExcluded)); + return new EnumeratedValueSet(false, larger._membersIncludedOrExcluded.Except(smaller._membersIncludedOrExcluded), _tc); case (true, true): - return new EnumeratedValueSet(true, larger._membersIncludedOrExcluded.Union(smaller._membersIncludedOrExcluded)); + return new EnumeratedValueSet(true, larger._membersIncludedOrExcluded.Union(smaller._membersIncludedOrExcluded), _tc); case (true, false): - return new EnumeratedValueSet(false, smaller._membersIncludedOrExcluded.Except(larger._membersIncludedOrExcluded)); + return new EnumeratedValueSet(false, smaller._membersIncludedOrExcluded.Except(larger._membersIncludedOrExcluded), _tc); } } @@ -154,9 +162,10 @@ public IValueSet Union(IValueSet o) public override bool Equals(object? obj) { - if (obj is not EnumeratedValueSet other) + if (obj is not EnumeratedValueSet other) return false; + Debug.Assert(object.ReferenceEquals(this._tc, other._tc)); return this._included == other._included && this._membersIncludedOrExcluded.SetEqualsWithoutIntermediateHashSet(other._membersIncludedOrExcluded); } diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSetFactory.cs index 748d2f62f0779..7bb2557e1deef 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSetFactory.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.EnumeratedValueSetFactory.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { @@ -15,42 +14,40 @@ internal static partial class ValueSetFactory /// /// A value set factory that only supports equality and works by including or excluding specific values. /// - private sealed class EnumeratedValueSetFactory : IValueSetFactory where TTC : struct, IEquatableValueTC where T : notnull + private sealed class EnumeratedValueSetFactory : IValueSetFactory where T : notnull { - public static readonly EnumeratedValueSetFactory Instance = new EnumeratedValueSetFactory(); + private readonly IEquatableValueTC _tc; - IValueSet IValueSetFactory.AllValues => EnumeratedValueSet.AllValues; + IValueSet IValueSetFactory.AllValues => EnumeratedValueSet.AllValues(_tc); - IValueSet IValueSetFactory.NoValues => EnumeratedValueSet.NoValues; + IValueSet IValueSetFactory.NoValues => EnumeratedValueSet.NoValues(_tc); - private EnumeratedValueSetFactory() { } + public EnumeratedValueSetFactory(IEquatableValueTC tc) { _tc = tc; } public IValueSet Related(BinaryOperatorKind relation, T value) { switch (relation) { case Equal: - return EnumeratedValueSet.Including(value); + return EnumeratedValueSet.Including(value, _tc); default: - return EnumeratedValueSet.AllValues; // supported for error recovery + return EnumeratedValueSet.AllValues(_tc); // supported for error recovery } } IValueSet IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue value) => - value.IsBad || value.IsNull ? EnumeratedValueSet.AllValues : this.Related(relation, default(TTC).FromConstantValue(value)); + value.IsBad || value.IsNull ? EnumeratedValueSet.AllValues(_tc) : this.Related(relation, _tc.FromConstantValue(value)); bool IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue left, ConstantValue right) { Debug.Assert(relation == BinaryOperatorKind.Equal); - TTC tc = default; - return tc.FromConstantValue(left).Equals(tc.FromConstantValue(right)); + return _tc.FromConstantValue(left).Equals(_tc.FromConstantValue(right)); } public IValueSet Random(int expectedSize, Random random) { - TTC tc = default; - T[] values = tc.RandomValues(expectedSize, random, expectedSize * 2); - IValueSet result = EnumeratedValueSet.NoValues; + T[] values = _tc.RandomValues(expectedSize, random, expectedSize * 2); + IValueSet result = EnumeratedValueSet.NoValues(_tc); Debug.Assert(result.IsEmpty); foreach (T value in values) result = result.Union(Related(Equal, value)); @@ -60,8 +57,7 @@ public IValueSet Random(int expectedSize, Random random) ConstantValue IValueSetFactory.RandomValue(Random random) { - TTC tc = default; - return tc.ToConstantValue(tc.RandomValues(1, random, 100)[0]); + return _tc.ToConstantValue(_tc.RandomValues(1, random, 100)[0]); } } } diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSet.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSet.cs index 4a7f7a4f7c850..5790692fd6c91 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSet.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSet.cs @@ -17,33 +17,33 @@ internal static partial class ValueSetFactory /// A value set implementation for and . /// /// A floating-point type. - /// A typeclass supporting that floating-point type. - private sealed class FloatingValueSet : IValueSet where TFloatingTC : struct, FloatingTC + private sealed class FloatingValueSet : IValueSet { private readonly IValueSet _numbers; private readonly bool _hasNaN; + private readonly FloatingTC _tc; - private FloatingValueSet(IValueSet numbers, bool hasNaN) + private FloatingValueSet(IValueSet numbers, bool hasNaN, FloatingTC tc) { - RoslynDebug.Assert(numbers is NumericValueSet); - (_numbers, _hasNaN) = (numbers, hasNaN); + RoslynDebug.Assert(numbers is NumericValueSet); + (_numbers, _hasNaN, _tc) = (numbers, hasNaN, tc); } - internal static readonly IValueSet AllValues = new FloatingValueSet( - numbers: NumericValueSet.AllValues, hasNaN: true); + internal static IValueSet AllValues(FloatingTC tc) => new FloatingValueSet( + numbers: NumericValueSet.AllValues(tc), hasNaN: true, tc); - internal static readonly IValueSet NoValues = new FloatingValueSet( - numbers: NumericValueSet.NoValues, hasNaN: false); + internal static IValueSet NoValues(FloatingTC tc) => new FloatingValueSet( + numbers: NumericValueSet.NoValues(tc), hasNaN: false, tc); - internal static IValueSet Random(int expectedSize, Random random) + internal static IValueSet Random(int expectedSize, Random random, FloatingTC tc) { bool hasNan = random.NextDouble() < 0.5; if (hasNan) expectedSize--; if (expectedSize < 1) expectedSize = 2; - return new FloatingValueSet( - numbers: (IValueSet)NumericValueSetFactory.Instance.Random(expectedSize, random), hasNaN: hasNan); + return new FloatingValueSet( + numbers: (IValueSet)new NumericValueSetFactory(tc).Random(expectedSize, random), hasNaN: hasNan, tc); } public bool IsEmpty => !_hasNaN && _numbers.IsEmpty; @@ -63,14 +63,12 @@ ConstantValue IValueSet.Sample } Debug.Assert(_hasNaN); - var tc = default(TFloatingTC); - return tc.ToConstantValue(tc.NaN); + return _tc.ToConstantValue(_tc.NaN); } } - public static IValueSet Related(BinaryOperatorKind relation, TFloating value) + public static IValueSet Related(BinaryOperatorKind relation, TFloating value, FloatingTC tc) { - TFloatingTC tc = default; if (tc.Related(Equal, tc.NaN, value)) { switch (relation) @@ -78,20 +76,22 @@ public static IValueSet Related(BinaryOperatorKind relation, TFloatin case BinaryOperatorKind.Equal: case BinaryOperatorKind.LessThanOrEqual: case BinaryOperatorKind.GreaterThanOrEqual: - return new FloatingValueSet( + return new FloatingValueSet( hasNaN: true, - numbers: NumericValueSet.NoValues + numbers: NumericValueSet.NoValues(tc), + tc: tc ); case BinaryOperatorKind.LessThan: case BinaryOperatorKind.GreaterThan: - return NoValues; + return NoValues(tc); default: throw ExceptionUtilities.UnexpectedValue(relation); } } - return new FloatingValueSet( - numbers: NumericValueSetFactory.Instance.Related(relation, value), - hasNaN: false + return new FloatingValueSet( + numbers: new NumericValueSetFactory(tc).Related(relation, value), + hasNaN: false, + tc: tc ); } @@ -99,10 +99,13 @@ public IValueSet Intersect(IValueSet o) { if (this == o) return this; - var other = (FloatingValueSet)o; - return new FloatingValueSet( + var other = (FloatingValueSet)o; + Debug.Assert(object.ReferenceEquals(this._tc, other._tc)); + + return new FloatingValueSet( numbers: this._numbers.Intersect(other._numbers), - hasNaN: this._hasNaN & other._hasNaN); + hasNaN: this._hasNaN & other._hasNaN, + _tc); } IValueSet IValueSet.Intersect(IValueSet other) => this.Intersect((IValueSet)other); @@ -111,48 +114,50 @@ public IValueSet Union(IValueSet o) { if (this == o) return this; - var other = (FloatingValueSet)o; - return new FloatingValueSet( + var other = (FloatingValueSet)o; + Debug.Assert(object.ReferenceEquals(this._tc, other._tc)); + + return new FloatingValueSet( numbers: this._numbers.Union(other._numbers), - hasNaN: this._hasNaN | other._hasNaN); + hasNaN: this._hasNaN | other._hasNaN, + _tc); } IValueSet IValueSet.Union(IValueSet other) => this.Union((IValueSet)other); public IValueSet Complement() { - return new FloatingValueSet( + return new FloatingValueSet( numbers: this._numbers.Complement(), - hasNaN: !this._hasNaN); + hasNaN: !this._hasNaN, + _tc); } IValueSet IValueSet.Complement() => this.Complement(); bool IValueSet.Any(BinaryOperatorKind relation, ConstantValue value) => - value.IsBad || this.Any(relation, default(TFloatingTC).FromConstantValue(value)); + value.IsBad || this.Any(relation, _tc.FromConstantValue(value)); public bool Any(BinaryOperatorKind relation, TFloating value) { - TFloatingTC tc = default; return - _hasNaN && tc.Related(relation, tc.NaN, value) || + _hasNaN && _tc.Related(relation, _tc.NaN, value) || _numbers.Any(relation, value); } - bool IValueSet.All(BinaryOperatorKind relation, ConstantValue value) => !value.IsBad && All(relation, default(TFloatingTC).FromConstantValue(value)); + bool IValueSet.All(BinaryOperatorKind relation, ConstantValue value) => !value.IsBad && All(relation, _tc.FromConstantValue(value)); public bool All(BinaryOperatorKind relation, TFloating value) { - TFloatingTC tc = default; return - (!_hasNaN || tc.Related(relation, tc.NaN, value)) && + (!_hasNaN || _tc.Related(relation, _tc.NaN, value)) && _numbers.All(relation, value); } public override int GetHashCode() => this._numbers.GetHashCode(); public override bool Equals(object? obj) => this == obj || - obj is FloatingValueSet other && + obj is FloatingValueSet other && this._hasNaN == other._hasNaN && this._numbers.Equals(other._numbers); diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSetFactory.cs index 9466d2c57ee01..0ca18637eecbf 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSetFactory.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.FloatingValueSetFactory.cs @@ -8,35 +8,38 @@ namespace Microsoft.CodeAnalysis.CSharp { internal static partial class ValueSetFactory { - private sealed class FloatingValueSetFactory : IValueSetFactory where TFloatingTC : struct, FloatingTC + private sealed class FloatingValueSetFactory : IValueSetFactory { - public static readonly FloatingValueSetFactory Instance = new FloatingValueSetFactory(); + private readonly FloatingTC _tc; - private FloatingValueSetFactory() { } + public FloatingValueSetFactory(FloatingTC tc) + { + _tc = tc; + } - IValueSet IValueSetFactory.AllValues => FloatingValueSet.AllValues; + IValueSet IValueSetFactory.AllValues => FloatingValueSet.AllValues(_tc); - IValueSet IValueSetFactory.NoValues => FloatingValueSet.NoValues; + IValueSet IValueSetFactory.NoValues => FloatingValueSet.NoValues(_tc); public IValueSet Related(BinaryOperatorKind relation, TFloating value) => - FloatingValueSet.Related(relation, value); + FloatingValueSet.Related(relation, value, _tc); IValueSet IValueSetFactory.Random(int expectedSize, Random random) => - FloatingValueSet.Random(expectedSize, random); + FloatingValueSet.Random(expectedSize, random, _tc); ConstantValue IValueSetFactory.RandomValue(Random random) { - TFloatingTC tc = default; - return tc.ToConstantValue(tc.Random(random)); + return _tc.ToConstantValue(_tc.Random(random)); } IValueSet IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue value) => - value.IsBad ? FloatingValueSet.AllValues : FloatingValueSet.Related(relation, default(TFloatingTC).FromConstantValue(value)); + value.IsBad + ? FloatingValueSet.AllValues(_tc) + : FloatingValueSet.Related(relation, _tc.FromConstantValue(value), _tc); bool IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue left, ConstantValue right) { - TFloatingTC tc = default; - return tc.Related(relation, tc.FromConstantValue(left), tc.FromConstantValue(right)); + return _tc.Related(relation, _tc.FromConstantValue(left), _tc.FromConstantValue(right)); } } } diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.IntTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.IntTC.cs index 78c59bdccd68a..d13f8de90e7f1 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.IntTC.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.IntTC.cs @@ -11,9 +11,21 @@ namespace Microsoft.CodeAnalysis.CSharp internal static partial class ValueSetFactory { - private struct IntTC : INumericTC + private class IntTC : INumericTC { - int INumericTC.MinValue => int.MinValue; + // Note: whenever we intersect or union two sets of IntTCs, + // we just keep the nonNegative flag of the set we're merging into. + public bool nonNegative; + + private IntTC(bool nonNegative) + { + this.nonNegative = nonNegative; + } + + public static readonly IntTC DefaultInstance = new IntTC(nonNegative: false); + public static readonly IntTC NonNegativeInstance = new IntTC(nonNegative: true); + + public int MinValue => nonNegative ? 0 : int.MinValue; int INumericTC.MaxValue => int.MaxValue; @@ -46,7 +58,7 @@ int INumericTC.Next(int value) int INumericTC.Prev(int value) { - Debug.Assert(value != int.MinValue); + Debug.Assert(value != MinValue); return value - 1; } @@ -58,7 +70,14 @@ int INumericTC.Prev(int value) public int Random(Random random) { - return (random.Next() << 10) ^ random.Next(); + if (nonNegative) + { + return Math.Abs((random.Next() << 10) ^ random.Next()); + } + else + { + return (random.Next() << 10) ^ random.Next(); + } } } } diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.LongTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.LongTC.cs index 77c0c624d738c..7a2a35cc6a286 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.LongTC.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.LongTC.cs @@ -11,8 +11,10 @@ namespace Microsoft.CodeAnalysis.CSharp internal static partial class ValueSetFactory { - private struct LongTC : INumericTC + private class LongTC : INumericTC { + public static readonly LongTC Instance = new LongTC(); + long INumericTC.MinValue => long.MinValue; long INumericTC.MaxValue => long.MaxValue; diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSet.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSet.cs index 34b5d01c8d42a..eef76f8595204 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSet.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSet.cs @@ -14,9 +14,9 @@ internal static partial class ValueSetFactory { private sealed class NintValueSet : IValueSet, IValueSet { - public static readonly NintValueSet AllValues = new NintValueSet(hasSmall: true, values: NumericValueSet.AllValues, hasLarge: true); + public static readonly NintValueSet AllValues = new NintValueSet(hasSmall: true, values: NumericValueSet.AllValues(IntTC.DefaultInstance), hasLarge: true); - public static readonly NintValueSet NoValues = new NintValueSet(hasSmall: false, values: NumericValueSet.NoValues, hasLarge: false); + public static readonly NintValueSet NoValues = new NintValueSet(hasSmall: false, values: NumericValueSet.NoValues(IntTC.DefaultInstance), hasLarge: false); private readonly IValueSet _values; diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSetFactory.cs index 85a4a7379d264..0d4cc21750cb0 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSetFactory.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NintValueSetFactory.cs @@ -24,7 +24,7 @@ public IValueSet Related(BinaryOperatorKind relation, int value) { return new NintValueSet( hasSmall: relation switch { LessThan => true, LessThanOrEqual => true, _ => false }, - values: NumericValueSetFactory.Instance.Related(relation, value), + values: new NumericValueSetFactory(IntTC.DefaultInstance).Related(relation, value), hasLarge: relation switch { GreaterThan => true, GreaterThanOrEqual => true, _ => false } ); } @@ -33,21 +33,21 @@ IValueSet IValueSetFactory.Random(int expectedSize, Random random) { return new NintValueSet( hasSmall: random.NextDouble() < 0.25, - values: (IValueSet)NumericValueSetFactory.Instance.Random(expectedSize, random), + values: (IValueSet)new NumericValueSetFactory(IntTC.DefaultInstance).Random(expectedSize, random), hasLarge: random.NextDouble() < 0.25 ); } - ConstantValue IValueSetFactory.RandomValue(Random random) => ConstantValue.CreateNativeInt(default(IntTC).Random(random)); + ConstantValue IValueSetFactory.RandomValue(Random random) => ConstantValue.CreateNativeInt(IntTC.DefaultInstance.Random(random)); IValueSet IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue value) { - return value.IsBad ? NintValueSet.AllValues : Related(relation, default(IntTC).FromConstantValue(value)); + return value.IsBad ? NintValueSet.AllValues : Related(relation, IntTC.DefaultInstance.FromConstantValue(value)); } bool IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue left, ConstantValue right) { - var tc = default(IntTC); + var tc = IntTC.DefaultInstance; return tc.Related(relation, tc.FromConstantValue(left), tc.FromConstantValue(right)); } } diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NonNegativeIntTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NonNegativeIntTC.cs deleted file mode 100644 index 02316b684aa7c..0000000000000 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NonNegativeIntTC.cs +++ /dev/null @@ -1,75 +0,0 @@ -// 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.Diagnostics; - -namespace Microsoft.CodeAnalysis.CSharp -{ - using static BinaryOperatorKind; - - internal static partial class ValueSetFactory - { - private struct NonNegativeIntTC : INumericTC - { - int INumericTC.MinValue => 0; - - int INumericTC.MaxValue => int.MaxValue; - - int INumericTC.Zero => 0; - - public bool Related(BinaryOperatorKind relation, int left, int right) - { - switch (relation) - { - case Equal: - return left == right; - case GreaterThanOrEqual: - return left >= right; - case GreaterThan: - return left > right; - case LessThanOrEqual: - return left <= right; - case LessThan: - return left < right; - default: - throw new ArgumentException("relation"); - } - } - - int INumericTC.Next(int value) - { - Debug.Assert(value != int.MaxValue); - return value + 1; - } - - int INumericTC.Prev(int value) - { - Debug.Assert(value != 0); - return value - 1; - } - - public int FromConstantValue(ConstantValue constantValue) - { - // We could have a negate value in source, but it won't get past NonNegativeIntValueSetFactory.Related - return constantValue.IsBad ? 0 : constantValue.Int32Value; - } - - public ConstantValue ToConstantValue(int value) - { - return ConstantValue.Create(value); - } - - string INumericTC.ToString(int value) - { - return value.ToString(); - } - - public int Random(Random random) - { - return Math.Abs((random.Next() << 10) ^ random.Next()); - } - } - } -} diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NonNegativeIntValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NonNegativeIntValueSetFactory.cs index caad9de5a3815..4697b463ecff7 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NonNegativeIntValueSetFactory.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NonNegativeIntValueSetFactory.cs @@ -14,50 +14,50 @@ internal static partial class ValueSetFactory private sealed class NonNegativeIntValueSetFactory : IValueSetFactory { public static readonly NonNegativeIntValueSetFactory Instance = new NonNegativeIntValueSetFactory(); + private static readonly IValueSetFactory s_underlying = new NumericValueSetFactory(IntTC.NonNegativeInstance); private NonNegativeIntValueSetFactory() { } - private readonly IValueSetFactory _underlying = NumericValueSetFactory.Instance; + public IValueSet AllValues => NumericValueSet.AllValues(IntTC.NonNegativeInstance); - public IValueSet AllValues => NumericValueSet.AllValues; - - public IValueSet NoValues => NumericValueSet.NoValues; + public IValueSet NoValues => NumericValueSet.NoValues(IntTC.NonNegativeInstance); public IValueSet Related(BinaryOperatorKind relation, int value) { + var tc = IntTC.NonNegativeInstance; switch (relation) { case LessThan: if (value <= 0) - return NumericValueSet.NoValues; - return new NumericValueSet(0, value - 1); + return NumericValueSet.NoValues(tc); + return new NumericValueSet(0, value - 1, tc); case LessThanOrEqual: if (value < 0) - return NumericValueSet.NoValues; - return new NumericValueSet(0, value); + return NumericValueSet.NoValues(tc); + return new NumericValueSet(0, value, tc); case GreaterThan: if (value == int.MaxValue) - return NumericValueSet.NoValues; - return new NumericValueSet(Math.Max(0, value + 1), int.MaxValue); + return NumericValueSet.NoValues(tc); + return new NumericValueSet(Math.Max(0, value + 1), int.MaxValue, tc); case GreaterThanOrEqual: - return new NumericValueSet(Math.Max(0, value), int.MaxValue); + return new NumericValueSet(Math.Max(0, value), int.MaxValue, tc); case Equal: if (value < 0) - return NumericValueSet.NoValues; - return new NumericValueSet(value, value); + return NumericValueSet.NoValues(tc); + return new NumericValueSet(value, value, tc); default: throw ExceptionUtilities.UnexpectedValue(relation); } } - IValueSet IValueSetFactory.Random(int expectedSize, Random random) => _underlying.Random(expectedSize, random); + IValueSet IValueSetFactory.Random(int expectedSize, Random random) => s_underlying.Random(expectedSize, random); - ConstantValue IValueSetFactory.RandomValue(Random random) => _underlying.RandomValue(random); + ConstantValue IValueSetFactory.RandomValue(Random random) => s_underlying.RandomValue(random); IValueSet IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue value) => - value.IsBad ? AllValues : Related(relation, default(NonNegativeIntTC).FromConstantValue(value)); + value.IsBad ? AllValues : Related(relation, IntTC.NonNegativeInstance.FromConstantValue(value)); - bool IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue left, ConstantValue right) => _underlying.Related(relation, left, right); + bool IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue left, ConstantValue right) => s_underlying.Related(relation, left, right); } } } diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSet.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSet.cs index e3a2de81aa340..f22ec1267e0f1 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSet.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSet.cs @@ -14,9 +14,9 @@ internal static partial class ValueSetFactory { private sealed class NuintValueSet : IValueSet, IValueSet { - public static readonly NuintValueSet AllValues = new NuintValueSet(values: NumericValueSet.AllValues, hasLarge: true); + public static readonly NuintValueSet AllValues = new NuintValueSet(values: NumericValueSet.AllValues(UIntTC.Instance), hasLarge: true); - public static readonly NuintValueSet NoValues = new NuintValueSet(values: NumericValueSet.NoValues, hasLarge: false); + public static readonly NuintValueSet NoValues = new NuintValueSet(values: NumericValueSet.NoValues(UIntTC.Instance), hasLarge: false); private readonly IValueSet _values; diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSetFactory.cs index bb3ede46f8fb3..d0f25f6f04a98 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSetFactory.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NuintValueSetFactory.cs @@ -23,7 +23,7 @@ private NuintValueSetFactory() { } public IValueSet Related(BinaryOperatorKind relation, uint value) { return new NuintValueSet( - values: NumericValueSetFactory.Instance.Related(relation, value), + values: new NumericValueSetFactory(UIntTC.Instance).Related(relation, value), hasLarge: relation switch { GreaterThan => true, GreaterThanOrEqual => true, _ => false } ); } @@ -31,21 +31,21 @@ public IValueSet Related(BinaryOperatorKind relation, uint value) IValueSet IValueSetFactory.Random(int expectedSize, Random random) { return new NuintValueSet( - values: (IValueSet)NumericValueSetFactory.Instance.Random(expectedSize, random), + values: (IValueSet)new NumericValueSetFactory(UIntTC.Instance).Random(expectedSize, random), hasLarge: random.NextDouble() < 0.25 ); } - ConstantValue IValueSetFactory.RandomValue(Random random) => ConstantValue.CreateNativeUInt(default(UIntTC).Random(random)); + ConstantValue IValueSetFactory.RandomValue(Random random) => ConstantValue.CreateNativeUInt(UIntTC.Instance.Random(random)); IValueSet IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue value) { - return value.IsBad ? NuintValueSet.AllValues : Related(relation, default(UIntTC).FromConstantValue(value)); + return value.IsBad ? NuintValueSet.AllValues : Related(relation, UIntTC.Instance.FromConstantValue(value)); } bool IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue left, ConstantValue right) { - var tc = default(UIntTC); + var tc = UIntTC.Instance; return tc.Related(relation, tc.FromConstantValue(left), tc.FromConstantValue(right)); } } diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSet.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSet.cs index d6fb6fa576699..988092ba6145e 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSet.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSet.cs @@ -19,23 +19,23 @@ internal static partial class ValueSetFactory /// /// The implementation of a value set for an numeric type . /// - private sealed class NumericValueSet : IValueSet where TTC : struct, INumericTC + private sealed class NumericValueSet : IValueSet { private readonly ImmutableArray<(T first, T last)> _intervals; + private readonly INumericTC _tc; - public static readonly NumericValueSet AllValues = new NumericValueSet(default(TTC).MinValue, default(TTC).MaxValue); + public static NumericValueSet AllValues(INumericTC tc) => new NumericValueSet(tc.MinValue, tc.MaxValue, tc); - public static readonly NumericValueSet NoValues = new NumericValueSet(ImmutableArray<(T first, T last)>.Empty); + public static NumericValueSet NoValues(INumericTC tc) => new NumericValueSet(ImmutableArray<(T first, T last)>.Empty, tc); - internal NumericValueSet(T first, T last) : this(ImmutableArray.Create((first, last))) + internal NumericValueSet(T first, T last, INumericTC tc) : this(ImmutableArray.Create((first, last)), tc) { - Debug.Assert(default(TTC).Related(LessThanOrEqual, first, last)); + Debug.Assert(tc.Related(LessThanOrEqual, first, last)); } - internal NumericValueSet(ImmutableArray<(T first, T last)> intervals) + internal NumericValueSet(ImmutableArray<(T first, T last)> intervals, INumericTC tc) { #if DEBUG - TTC tc = default; Debug.Assert(intervals.Length == 0 || tc.Related(GreaterThanOrEqual, intervals[0].first, tc.MinValue)); for (int i = 0, n = intervals.Length; i < n; i++) { @@ -48,6 +48,7 @@ internal NumericValueSet(ImmutableArray<(T first, T last)> intervals) } #endif _intervals = intervals; + _tc = tc; } public bool IsEmpty => _intervals.Length == 0; @@ -60,26 +61,24 @@ ConstantValue IValueSet.Sample throw new ArgumentException(); // Prefer a value near zero. - var tc = default(TTC); - var gz = NumericValueSetFactory.Instance.Related(BinaryOperatorKind.GreaterThanOrEqual, tc.Zero); - var t = (NumericValueSet)this.Intersect(gz); + var gz = new NumericValueSetFactory(_tc).Related(BinaryOperatorKind.GreaterThanOrEqual, _tc.Zero); + var t = (NumericValueSet)this.Intersect(gz); if (!t.IsEmpty) - return tc.ToConstantValue(t._intervals[0].first); - return tc.ToConstantValue(this._intervals[this._intervals.Length - 1].last); + return _tc.ToConstantValue(t._intervals[0].first); + return _tc.ToConstantValue(this._intervals[this._intervals.Length - 1].last); } } public bool Any(BinaryOperatorKind relation, T value) { - TTC tc = default; switch (relation) { case LessThan: case LessThanOrEqual: - return _intervals.Length > 0 && tc.Related(relation, _intervals[0].first, value); + return _intervals.Length > 0 && _tc.Related(relation, _intervals[0].first, value); case GreaterThan: case GreaterThanOrEqual: - return _intervals.Length > 0 && tc.Related(relation, _intervals[_intervals.Length - 1].last, value); + return _intervals.Length > 0 && _tc.Related(relation, _intervals[_intervals.Length - 1].last, value); case Equal: return anyIntervalContains(0, _intervals.Length - 1, value); default: @@ -94,10 +93,10 @@ bool anyIntervalContains(int firstIntervalIndex, int lastIntervalIndex, T value) return false; if (lastIntervalIndex == firstIntervalIndex) - return tc.Related(GreaterThanOrEqual, value, _intervals[lastIntervalIndex].first) && tc.Related(LessThanOrEqual, value, _intervals[lastIntervalIndex].last); + return _tc.Related(GreaterThanOrEqual, value, _intervals[lastIntervalIndex].first) && _tc.Related(LessThanOrEqual, value, _intervals[lastIntervalIndex].last); int midIndex = firstIntervalIndex + (lastIntervalIndex - firstIntervalIndex) / 2; - if (tc.Related(LessThanOrEqual, value, _intervals[midIndex].last)) + if (_tc.Related(LessThanOrEqual, value, _intervals[midIndex].last)) lastIntervalIndex = midIndex; else firstIntervalIndex = midIndex + 1; @@ -105,67 +104,66 @@ bool anyIntervalContains(int firstIntervalIndex, int lastIntervalIndex, T value) } } - bool IValueSet.Any(BinaryOperatorKind relation, ConstantValue value) => value.IsBad || Any(relation, default(TTC).FromConstantValue(value)); + bool IValueSet.Any(BinaryOperatorKind relation, ConstantValue value) => value.IsBad || Any(relation, _tc.FromConstantValue(value)); public bool All(BinaryOperatorKind relation, T value) { if (_intervals.Length == 0) return true; - TTC tc = default; switch (relation) { case LessThan: case LessThanOrEqual: - return tc.Related(relation, _intervals[_intervals.Length - 1].last, value); + return _tc.Related(relation, _intervals[_intervals.Length - 1].last, value); case GreaterThan: case GreaterThanOrEqual: - return tc.Related(relation, _intervals[0].first, value); + return _tc.Related(relation, _intervals[0].first, value); case Equal: - return _intervals.Length == 1 && tc.Related(Equal, _intervals[0].first, value) && tc.Related(Equal, _intervals[0].last, value); + return _intervals.Length == 1 && _tc.Related(Equal, _intervals[0].first, value) && _tc.Related(Equal, _intervals[0].last, value); default: throw ExceptionUtilities.UnexpectedValue(relation); } } - bool IValueSet.All(BinaryOperatorKind relation, ConstantValue value) => !value.IsBad && All(relation, default(TTC).FromConstantValue(value)); + bool IValueSet.All(BinaryOperatorKind relation, ConstantValue value) => !value.IsBad && All(relation, _tc.FromConstantValue(value)); public IValueSet Complement() { if (_intervals.Length == 0) - return AllValues; + return AllValues(_tc); - TTC tc = default; var builder = ArrayBuilder<(T first, T last)>.GetInstance(); // add a prefix if apropos. - if (tc.Related(LessThan, tc.MinValue, _intervals[0].first)) + if (_tc.Related(LessThan, _tc.MinValue, _intervals[0].first)) { - builder.Add((tc.MinValue, tc.Prev(_intervals[0].first))); + builder.Add((_tc.MinValue, _tc.Prev(_intervals[0].first))); } // add the in-between intervals int lastIndex = _intervals.Length - 1; for (int i = 0; i < lastIndex; i++) { - builder.Add((tc.Next(_intervals[i].last), tc.Prev(_intervals[i + 1].first))); + builder.Add((_tc.Next(_intervals[i].last), _tc.Prev(_intervals[i + 1].first))); } // add a suffix if apropos - if (tc.Related(LessThan, _intervals[lastIndex].last, tc.MaxValue)) + if (_tc.Related(LessThan, _intervals[lastIndex].last, _tc.MaxValue)) { - builder.Add((tc.Next(_intervals[lastIndex].last), tc.MaxValue)); + builder.Add((_tc.Next(_intervals[lastIndex].last), _tc.MaxValue)); } - return new NumericValueSet(builder.ToImmutableAndFree()); + return new NumericValueSet(builder.ToImmutableAndFree(), _tc); } IValueSet IValueSet.Complement() => this.Complement(); public IValueSet Intersect(IValueSet o) { - var other = (NumericValueSet)o; - TTC tc = default; + var other = (NumericValueSet)o; + Debug.Assert(this._tc.GetType() == other._tc.GetType()); + var builder = ArrayBuilder<(T first, T last)>.GetInstance(); var left = this._intervals; var right = other._intervals; @@ -175,22 +173,22 @@ public IValueSet Intersect(IValueSet o) { var leftInterval = left[l]; var rightInterval = right[r]; - if (tc.Related(LessThan, leftInterval.last, rightInterval.first)) + if (_tc.Related(LessThan, leftInterval.last, rightInterval.first)) { l++; } - else if (tc.Related(LessThan, rightInterval.last, leftInterval.first)) + else if (_tc.Related(LessThan, rightInterval.last, leftInterval.first)) { r++; } else { - Add(builder, Max(leftInterval.first, rightInterval.first), Min(leftInterval.last, rightInterval.last)); - if (tc.Related(LessThan, leftInterval.last, rightInterval.last)) + Add(builder, Max(leftInterval.first, rightInterval.first, _tc), Min(leftInterval.last, rightInterval.last, _tc), _tc); + if (_tc.Related(LessThan, leftInterval.last, rightInterval.last)) { l++; } - else if (tc.Related(LessThan, rightInterval.last, leftInterval.last)) + else if (_tc.Related(LessThan, rightInterval.last, leftInterval.last)) { r++; } @@ -202,15 +200,14 @@ public IValueSet Intersect(IValueSet o) } } - return new NumericValueSet(builder.ToImmutableAndFree()); + return new NumericValueSet(builder.ToImmutableAndFree(), _tc); } /// /// Add an interval to the end of the builder. /// - private static void Add(ArrayBuilder<(T first, T last)> builder, T first, T last) + private static void Add(ArrayBuilder<(T first, T last)> builder, T first, T last, INumericTC tc) { - TTC tc = default; Debug.Assert(tc.Related(LessThanOrEqual, first, last)); Debug.Assert(tc.Related(GreaterThanOrEqual, first, tc.MinValue)); Debug.Assert(tc.Related(LessThanOrEqual, last, tc.MaxValue)); @@ -219,7 +216,7 @@ private static void Add(ArrayBuilder<(T first, T last)> builder, T first, T last { // merge with previous interval when adjacent var oldLastInterval = builder.Pop(); - oldLastInterval.last = Max(last, oldLastInterval.last); + oldLastInterval.last = Max(last, oldLastInterval.last, tc); builder.Push(oldLastInterval); } else @@ -227,15 +224,13 @@ private static void Add(ArrayBuilder<(T first, T last)> builder, T first, T last builder.Add((first, last)); } } - private static T Min(T a, T b) + private static T Min(T a, T b, INumericTC tc) { - TTC tc = default; return tc.Related(LessThan, a, b) ? a : b; } - private static T Max(T a, T b) + private static T Max(T a, T b, INumericTC tc) { - TTC tc = default; return tc.Related(LessThan, a, b) ? b : a; } @@ -243,8 +238,9 @@ private static T Max(T a, T b) public IValueSet Union(IValueSet o) { - var other = (NumericValueSet)o; - TTC tc = default; + var other = (NumericValueSet)o; + Debug.Assert(this._tc.GetType() == other._tc.GetType()); + var builder = ArrayBuilder<(T first, T last)>.GetInstance(); var left = this._intervals; var right = other._intervals; @@ -254,19 +250,19 @@ public IValueSet Union(IValueSet o) { var leftInterval = left[l]; var rightInterval = right[r]; - if (tc.Related(LessThan, leftInterval.last, rightInterval.first)) + if (_tc.Related(LessThan, leftInterval.last, rightInterval.first)) { - Add(builder, leftInterval.first, leftInterval.last); + Add(builder, leftInterval.first, leftInterval.last, _tc); l++; } - else if (tc.Related(LessThan, rightInterval.last, leftInterval.first)) + else if (_tc.Related(LessThan, rightInterval.last, leftInterval.first)) { - Add(builder, rightInterval.first, rightInterval.last); + Add(builder, rightInterval.first, rightInterval.last, _tc); r++; } else { - Add(builder, Min(leftInterval.first, rightInterval.first), Max(leftInterval.last, rightInterval.last)); + Add(builder, Min(leftInterval.first, rightInterval.first, _tc), Max(leftInterval.last, rightInterval.last, _tc), _tc); l++; r++; } @@ -275,18 +271,18 @@ public IValueSet Union(IValueSet o) while (l < left.Length) { var leftInterval = left[l]; - Add(builder, leftInterval.first, leftInterval.last); + Add(builder, leftInterval.first, leftInterval.last, _tc); l++; } while (r < right.Length) { var rightInterval = right[r]; - Add(builder, rightInterval.first, rightInterval.last); + Add(builder, rightInterval.first, rightInterval.last, _tc); r++; } - return new NumericValueSet(builder.ToImmutableAndFree()); + return new NumericValueSet(builder.ToImmutableAndFree(), _tc); } IValueSet IValueSet.Union(IValueSet other) => this.Union((IValueSet)other); @@ -294,9 +290,8 @@ public IValueSet Union(IValueSet o) /// /// Produce a random value set for testing purposes. /// - internal static IValueSet Random(int expectedSize, Random random) + internal static IValueSet Random(int expectedSize, Random random, INumericTC tc) { - TTC tc = default; T[] values = new T[expectedSize * 2]; for (int i = 0, n = expectedSize * 2; i < n; i++) { @@ -308,10 +303,10 @@ internal static IValueSet Random(int expectedSize, Random random) { T first = values[i]; T last = values[i + 1]; - Add(builder, first, last); + Add(builder, first, last, tc); } - return new NumericValueSet(builder.ToImmutableAndFree()); + return new NumericValueSet(builder.ToImmutableAndFree(), tc); } /// @@ -319,12 +314,11 @@ internal static IValueSet Random(int expectedSize, Random random) /// public override string ToString() { - TTC tc = default; - return string.Join(",", this._intervals.Select(p => $"[{tc.ToString(p.first)}..{tc.ToString(p.last)}]")); + return string.Join(",", this._intervals.Select(p => $"[{_tc.ToString(p.first)}..{_tc.ToString(p.last)}]")); } public override bool Equals(object? obj) => - obj is NumericValueSet other && + obj is NumericValueSet other && this._intervals.SequenceEqual(other._intervals); public override int GetHashCode() diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSetFactory.cs index 09afe5b3f83da..1534346ee84f7 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSetFactory.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSetFactory.cs @@ -16,56 +16,53 @@ internal static partial class ValueSetFactory /// parameterized by a type class /// that provides the primitives for that type. /// - private sealed class NumericValueSetFactory : IValueSetFactory where TTC : struct, INumericTC + private struct NumericValueSetFactory : IValueSetFactory { - public static readonly NumericValueSetFactory Instance = new NumericValueSetFactory(); + private readonly INumericTC _tc; - IValueSet IValueSetFactory.AllValues => NumericValueSet.AllValues; + IValueSet IValueSetFactory.AllValues => NumericValueSet.AllValues(_tc); - IValueSet IValueSetFactory.NoValues => NumericValueSet.NoValues; + IValueSet IValueSetFactory.NoValues => NumericValueSet.NoValues(_tc); - private NumericValueSetFactory() { } + public NumericValueSetFactory(INumericTC tc) { this._tc = tc; } public IValueSet Related(BinaryOperatorKind relation, T value) { - TTC tc = default; switch (relation) { case LessThan: - if (tc.Related(LessThanOrEqual, value, tc.MinValue)) - return NumericValueSet.NoValues; - return new NumericValueSet(tc.MinValue, tc.Prev(value)); + if (_tc.Related(LessThanOrEqual, value, _tc.MinValue)) + return NumericValueSet.NoValues(_tc); + return new NumericValueSet(_tc.MinValue, _tc.Prev(value), _tc); case LessThanOrEqual: - return new NumericValueSet(tc.MinValue, value); + return new NumericValueSet(_tc.MinValue, value, _tc); case GreaterThan: - if (tc.Related(GreaterThanOrEqual, value, tc.MaxValue)) - return NumericValueSet.NoValues; - return new NumericValueSet(tc.Next(value), tc.MaxValue); + if (_tc.Related(GreaterThanOrEqual, value, _tc.MaxValue)) + return NumericValueSet.NoValues(_tc); + return new NumericValueSet(_tc.Next(value), _tc.MaxValue, _tc); case GreaterThanOrEqual: - return new NumericValueSet(value, tc.MaxValue); + return new NumericValueSet(value, _tc.MaxValue, _tc); case Equal: - return new NumericValueSet(value, value); + return new NumericValueSet(value, value, _tc); default: throw ExceptionUtilities.UnexpectedValue(relation); } } IValueSet IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue value) => - value.IsBad ? NumericValueSet.AllValues : Related(relation, default(TTC).FromConstantValue(value)); + value.IsBad ? NumericValueSet.AllValues(_tc) : Related(relation, _tc.FromConstantValue(value)); public IValueSet Random(int expectedSize, Random random) => - NumericValueSet.Random(expectedSize, random); + NumericValueSet.Random(expectedSize, random, _tc); ConstantValue IValueSetFactory.RandomValue(Random random) { - var tc = default(TTC); - return tc.ToConstantValue(tc.Random(random)); + return _tc.ToConstantValue(_tc.Random(random)); } bool IValueSetFactory.Related(BinaryOperatorKind relation, ConstantValue left, ConstantValue right) { - var tc = default(TTC); - return tc.Related(relation, tc.FromConstantValue(left), tc.FromConstantValue(right)); + return _tc.Related(relation, _tc.FromConstantValue(left), _tc.FromConstantValue(right)); } } } diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SByteTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SByteTC.cs index 09952d78a7f87..dc87def798a11 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SByteTC.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SByteTC.cs @@ -11,8 +11,9 @@ namespace Microsoft.CodeAnalysis.CSharp internal static partial class ValueSetFactory { - private struct SByteTC : INumericTC + private class SByteTC : INumericTC { + public static readonly SByteTC Instance = new SByteTC(); sbyte INumericTC.MinValue => sbyte.MinValue; sbyte INumericTC.MaxValue => sbyte.MaxValue; diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ShortTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ShortTC.cs index b130ae1889bf6..b7e2d0a9024a1 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ShortTC.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ShortTC.cs @@ -11,8 +11,10 @@ namespace Microsoft.CodeAnalysis.CSharp internal static partial class ValueSetFactory { - private struct ShortTC : INumericTC + private class ShortTC : INumericTC { + public static readonly ShortTC Instance = new ShortTC(); + short INumericTC.MinValue => short.MinValue; short INumericTC.MaxValue => short.MaxValue; diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SingleTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SingleTC.cs index 255bff9efd335..5c48c46de051c 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SingleTC.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.SingleTC.cs @@ -11,8 +11,10 @@ namespace Microsoft.CodeAnalysis.CSharp internal static partial class ValueSetFactory { - private struct SingleTC : FloatingTC, INumericTC + private class SingleTC : FloatingTC, INumericTC { + public static readonly SingleTC Instance = new SingleTC(); + float INumericTC.MinValue => float.NegativeInfinity; float INumericTC.MaxValue => float.PositiveInfinity; diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.StringTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.StringTC.cs index 2c55f7b3f16e8..955fa92c3881f 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.StringTC.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.StringTC.cs @@ -9,8 +9,11 @@ namespace Microsoft.CodeAnalysis.CSharp { internal static partial class ValueSetFactory { - private struct StringTC : IEquatableValueTC + private class StringTC : IEquatableValueTC { + public static readonly StringTC Instance = new StringTC(); + private StringTC() { } + string IEquatableValueTC.FromConstantValue(ConstantValue constantValue) { var result = constantValue.IsBad ? string.Empty : constantValue.StringValue; diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UIntTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UIntTC.cs index ab2c52af873b6..d7b343ed2f80c 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UIntTC.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UIntTC.cs @@ -11,8 +11,10 @@ namespace Microsoft.CodeAnalysis.CSharp internal static partial class ValueSetFactory { - private struct UIntTC : INumericTC + private class UIntTC : INumericTC { + public static readonly UIntTC Instance = new UIntTC(); + uint INumericTC.MinValue => uint.MinValue; uint INumericTC.MaxValue => uint.MaxValue; diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ULongTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ULongTC.cs index 2f48fa34b9095..9d0fef4486b3e 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ULongTC.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.ULongTC.cs @@ -11,8 +11,10 @@ namespace Microsoft.CodeAnalysis.CSharp internal static partial class ValueSetFactory { - private struct ULongTC : INumericTC + private class ULongTC : INumericTC { + public static readonly ULongTC Instance = new ULongTC(); + ulong INumericTC.MinValue => ulong.MinValue; ulong INumericTC.MaxValue => ulong.MaxValue; diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UShortTC.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UShortTC.cs index 94b0a03f31e57..c621259302a5a 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UShortTC.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.UShortTC.cs @@ -11,8 +11,10 @@ namespace Microsoft.CodeAnalysis.CSharp internal static partial class ValueSetFactory { - private struct UShortTC : INumericTC + private class UShortTC : INumericTC { + public static readonly UShortTC Instance = new UShortTC(); + ushort INumericTC.MinValue => ushort.MinValue; ushort INumericTC.MaxValue => ushort.MaxValue; diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.cs index 23468cca65cdc..9282148453f6b 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.cs @@ -11,19 +11,19 @@ namespace Microsoft.CodeAnalysis.CSharp /// internal static partial class ValueSetFactory { - internal static readonly IValueSetFactory ForByte = NumericValueSetFactory.Instance; - internal static readonly IValueSetFactory ForSByte = NumericValueSetFactory.Instance; - internal static readonly IValueSetFactory ForChar = NumericValueSetFactory.Instance; - internal static readonly IValueSetFactory ForShort = NumericValueSetFactory.Instance; - internal static readonly IValueSetFactory ForUShort = NumericValueSetFactory.Instance; - internal static readonly IValueSetFactory ForInt = NumericValueSetFactory.Instance; - internal static readonly IValueSetFactory ForUInt = NumericValueSetFactory.Instance; - internal static readonly IValueSetFactory ForLong = NumericValueSetFactory.Instance; - internal static readonly IValueSetFactory ForULong = NumericValueSetFactory.Instance; + internal static readonly IValueSetFactory ForByte = new NumericValueSetFactory(ByteTC.Instance); + internal static readonly IValueSetFactory ForSByte = new NumericValueSetFactory(SByteTC.Instance); + internal static readonly IValueSetFactory ForChar = new NumericValueSetFactory(CharTC.Instance); + internal static readonly IValueSetFactory ForShort = new NumericValueSetFactory(ShortTC.Instance); + internal static readonly IValueSetFactory ForUShort = new NumericValueSetFactory(UShortTC.Instance); + internal static readonly IValueSetFactory ForInt = new NumericValueSetFactory(IntTC.DefaultInstance); + internal static readonly IValueSetFactory ForUInt = new NumericValueSetFactory(UIntTC.Instance); + internal static readonly IValueSetFactory ForLong = new NumericValueSetFactory(LongTC.Instance); + internal static readonly IValueSetFactory ForULong = new NumericValueSetFactory(ULongTC.Instance); internal static readonly IValueSetFactory ForBool = BoolValueSetFactory.Instance; - internal static readonly IValueSetFactory ForFloat = FloatingValueSetFactory.Instance; - internal static readonly IValueSetFactory ForDouble = FloatingValueSetFactory.Instance; - internal static readonly IValueSetFactory ForString = EnumeratedValueSetFactory.Instance; + internal static readonly IValueSetFactory ForFloat = new FloatingValueSetFactory(SingleTC.Instance); + internal static readonly IValueSetFactory ForDouble = new FloatingValueSetFactory(DoubleTC.Instance); + internal static readonly IValueSetFactory ForString = new EnumeratedValueSetFactory(StringTC.Instance); internal static readonly IValueSetFactory ForDecimal = DecimalValueSetFactory.Instance; internal static readonly IValueSetFactory ForNint = NintValueSetFactory.Instance; internal static readonly IValueSetFactory ForNuint = NuintValueSetFactory.Instance; diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests_ListPatterns.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests_ListPatterns.cs index 837f6b46095f8..0dcfda3f30f71 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests_ListPatterns.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/PatternMatchingTests_ListPatterns.cs @@ -8915,4 +8915,383 @@ public void NotExhaustive_LongList() // _ = a switch { { Length: < 1000 } => 0 }; Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ Length: 1000 }").WithLocation(2, 7)); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71660")] + public void MixedCountPatterns() + { + // One of the property patterns is treated as a non-negative Count pattern, + // while the other is a regular property pattern. + var source = """ +using System; +using System.Collections; +using System.Collections.Generic; + +System.Console.Write(select(new List())); +System.Console.Write(select(new List() { 42 })); +System.Console.Write(select(new C())); + +int select(ICollection c) +{ + try + { + return c switch + { + { Count: 0 } => 0, + IList { Count: > 0 } => 1, + }; + } + catch + { + return 2; + } +} + +class C : System.Collections.ICollection +{ + public int Count => 1; + public object SyncRoot => throw new NotImplementedException(); + public bool IsSynchronized => throw new NotImplementedException(); + public void CopyTo(Array array, int index) => throw new NotImplementedException(); + public IEnumerator GetEnumerator() => throw new NotImplementedException(); +} +"""; + CompileAndVerify(source, expectedOutput: "012").VerifyDiagnostics( + // (13,18): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ Count: 1 }' is not covered. + // return c switch + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ Count: 1 }").WithLocation(13, 18) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71660")] + public void MixedCountPatterns_ReverseOrder() + { + var source = """ +using System; +using System.Collections; +using System.Collections.Generic; + +System.Console.Write(select(new List())); +System.Console.Write(select(new List() { 42 })); +System.Console.Write(select(new C())); + +int select(ICollection c) +{ + try + { + return c switch + { + IList { Count: > 0 } => 1, + { Count: 0 } => 0, + }; + } + catch + { + return 2; + } +} + +class C : System.Collections.ICollection +{ + public int Count => 1; + public object SyncRoot => throw new NotImplementedException(); + public bool IsSynchronized => throw new NotImplementedException(); + public void CopyTo(Array array, int index) => throw new NotImplementedException(); + public IEnumerator GetEnumerator() => throw new NotImplementedException(); +} +"""; + CompileAndVerify(source, expectedOutput: "012").VerifyDiagnostics( + // (13,18): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ Count: 1 }' is not covered. + // return c switch + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ Count: 1 }").WithLocation(13, 18) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71660")] + public void MixedCountPatterns_NegativeTest() + { + var source = """ +using System.Collections; +using System.Collections.Generic; + +var result = (ICollection)new List() switch +{ + IList { Count: > 0 } => throw null, + IList { Count: < 0 } => throw null, + { Count: 0 } => "ran", +}; + +System.Console.Write(result); +"""; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (4,43): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ Count: 1 }' is not covered. + // var result = (ICollection)new List() switch + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ Count: 1 }").WithLocation(4, 43), + // (7,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // IList { Count: < 0 } => throw null, + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "IList { Count: < 0 }").WithLocation(7, 5) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71660")] + public void MixedCountPatterns_NegativeTestAfterRegularPropertyPattern() + { + var source = """ +using System.Collections; +using System.Collections.Generic; + +_ = (ICollection)new List() switch +{ + IList { Count: > 0 } => 0, + { Count: 0 } => 0, + IList { Count: < 0 } => 0, +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (4,34): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ Count: 1 }' is not covered. + // _ = (ICollection)new List() switch + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ Count: 1 }").WithLocation(4, 34), + // (8,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // IList { Count: < 0 } => 0, + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "IList { Count: < 0 }").WithLocation(8, 5) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71660")] + public void MixedCountPatterns_ExplicitICollectionType() + { + var source = """ +using System; +using System.Collections; +using System.Collections.Generic; + +System.Console.Write(select(new List())); +System.Console.Write(select(new List() { 42 })); +System.Console.Write(select(new C())); + +int select(ICollection c) +{ + try + { + return c switch + { + ICollection { Count: 0 } => 0, + IList { Count: 1 } => 1, + }; + } + catch + { + return 2; + } +} + +class C : System.Collections.ICollection +{ + public int Count => 1; + public object SyncRoot => throw new NotImplementedException(); + public bool IsSynchronized => throw new NotImplementedException(); + public void CopyTo(Array array, int index) => throw new NotImplementedException(); + public IEnumerator GetEnumerator() => throw new NotImplementedException(); +} +"""; + CompileAndVerify(source, expectedOutput: "012").VerifyDiagnostics( + // (13,18): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ Count: 1 }' is not covered. + // return c switch + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ Count: 1 }").WithLocation(13, 18) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71660")] + public void MixedCountPatterns_Subsumed() + { + var source = """ +using System.Collections; + +_ = (ICollection)null switch +{ + { Count: <0 or >0 } => 0, + IList { Count: >0 } => 1, + { Count: 0 } => 2, +}; +"""; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (6,5): error CS8510: The pattern is unreachable. It has already been handled by a previous arm of the switch expression or it is impossible to match. + // IList { Count: >0 } => 1, + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "IList { Count: >0 }").WithLocation(6, 5) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71660")] + public void MixedCountPatterns_Or() + { + var source = """ +using System; +using System.Collections; +using System.Collections.Generic; + +System.Console.Write(select(new List())); +System.Console.Write(select(new List() { 42 })); +System.Console.Write(select(new C())); + +int select(ICollection c) +{ + try + { + return c switch + { + { Count: 0 } or (IList { Count: > 0 }) => 1, + }; + } + catch + { + return 2; + } +} + +class C : System.Collections.ICollection +{ + public int Count => 1; + public object SyncRoot => throw new NotImplementedException(); + public bool IsSynchronized => throw new NotImplementedException(); + public void CopyTo(Array array, int index) => throw new NotImplementedException(); + public IEnumerator GetEnumerator() => throw new NotImplementedException(); +} +"""; + CompileAndVerify(source, expectedOutput: "112").VerifyDiagnostics( + // (13,18): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ Count: 1 }' is not covered. + // return c switch + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ Count: 1 }").WithLocation(13, 18) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71660")] + public void MixedCountPatterns_Or_ReverseOrder() + { + var source = """ +using System; +using System.Collections; +using System.Collections.Generic; + +System.Console.Write(select(new List())); +System.Console.Write(select(new List() { 42 })); +System.Console.Write(select(new C())); + +int select(ICollection c) +{ + try + { + return c switch + { + (IList { Count: > 0 }) or { Count: 0 } => 1, + }; + } + catch + { + return 2; + } +} + +class C : System.Collections.ICollection +{ + public int Count => 1; + public object SyncRoot => throw new NotImplementedException(); + public bool IsSynchronized => throw new NotImplementedException(); + public void CopyTo(Array array, int index) => throw new NotImplementedException(); + public IEnumerator GetEnumerator() => throw new NotImplementedException(); +} +"""; + CompileAndVerify(source, expectedOutput: "112").VerifyDiagnostics( + // (13,18): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ Count: 1 }' is not covered. + // return c switch + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ Count: 1 }").WithLocation(13, 18) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71660")] + public void MixedCountPatterns_And() + { + var source = """ +using System; +using System.Collections; +using System.Collections.Generic; + +System.Console.Write(select(new List())); +System.Console.Write(select(new List() { 42 })); +System.Console.Write(select(new C())); + +int select(ICollection c) +{ + try + { + return c switch + { + { Count: >= 0 } and (IList { Count: > 0 }) => 1, + }; + } + catch + { + return 2; + } +} + +class C : System.Collections.ICollection +{ + public int Count => 1; + public object SyncRoot => throw new NotImplementedException(); + public bool IsSynchronized => throw new NotImplementedException(); + public void CopyTo(Array array, int index) => throw new NotImplementedException(); + public IEnumerator GetEnumerator() => throw new NotImplementedException(); +} +"""; + CompileAndVerify(source, expectedOutput: "212").VerifyDiagnostics( + // (13,18): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ Count: -1 }' is not covered. + // return c switch + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ Count: -1 }").WithLocation(13, 18) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71660")] + public void MixedCountPatterns_And_ReverseOrder() + { + var source = """ +using System; +using System.Collections; +using System.Collections.Generic; + +System.Console.Write(select(new List())); +System.Console.Write(select(new List() { 42 })); +System.Console.Write(select(new C())); + +int select(ICollection c) +{ + try + { + return c switch + { + (IList { Count: > 0 }) and { Count: >= 0 } => 1, + }; + } + catch + { + return 2; + } +} + +class C : System.Collections.ICollection +{ + public int Count => 1; + public object SyncRoot => throw new NotImplementedException(); + public bool IsSynchronized => throw new NotImplementedException(); + public void CopyTo(Array array, int index) => throw new NotImplementedException(); + public IEnumerator GetEnumerator() => throw new NotImplementedException(); +} +"""; + CompileAndVerify(source, expectedOutput: "212").VerifyDiagnostics( + // (13,18): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '_' is not covered. + // return c switch + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("_").WithLocation(13, 18) + ); + } }