diff --git a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs index 90df61c6a26ef..a8136de049ba8 100644 --- a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs +++ b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; + namespace Microsoft.CodeAnalysis.CSharp { /// @@ -814,8 +815,7 @@ DagState uniqifyState(ImmutableArray cases, ImmutableDictionary whenTrueDecisions, out ImmutableArray whenFalseDecisions, out ImmutableDictionary whenTrueValues, @@ -938,6 +938,7 @@ BoundDecisionDagNode finalState(SyntaxNode syntax, LabelSymbol label, ImmutableA } private void SplitCase( + DagState state, StateForCase stateForCase, BoundDagTest test, IValueSet? whenTrueValues, @@ -946,7 +947,7 @@ private void SplitCase( out StateForCase whenFalse, ref bool foundExplicitNullTest) { - stateForCase.RemainingTests.Filter(this, test, whenTrueValues, whenFalseValues, out Tests whenTrueTests, out Tests whenFalseTests, ref foundExplicitNullTest); + stateForCase.RemainingTests.Filter(this, test, state, whenTrueValues, whenFalseValues, out Tests whenTrueTests, out Tests whenFalseTests, ref foundExplicitNullTest); whenTrue = makeNext(whenTrueTests); whenFalse = makeNext(whenFalseTests); return; @@ -962,30 +963,31 @@ StateForCase makeNext(Tests remainingTests) } private void SplitCases( - ImmutableArray statesForCases, - ImmutableDictionary values, - BoundDagTest test, + DagState state, out ImmutableArray whenTrue, out ImmutableArray whenFalse, out ImmutableDictionary whenTrueValues, out ImmutableDictionary whenFalseValues, ref bool foundExplicitNullTest) { + RoslynDebug.AssertNotNull(state.SelectedTest); + var test = state.SelectedTest; + var statesForCases = state.Cases; var whenTrueBuilder = ArrayBuilder.GetInstance(statesForCases.Length); var whenFalseBuilder = ArrayBuilder.GetInstance(statesForCases.Length); - bool whenTruePossible, whenFalsePossible; - (whenTrueValues, whenFalseValues, whenTruePossible, whenFalsePossible) = SplitValues(values, test); + (whenTrueValues, whenFalseValues, bool whenTruePossible, bool whenFalsePossible) = SplitValues(state.RemainingValues, test); // whenTruePossible means the test could possibly have succeeded. whenFalsePossible means it could possibly have failed. // Tests that are either impossible or tautological (i.e. either of these false) given // the set of values are normally removed and replaced by the known result, so we would not normally be processing // a test that always succeeds or always fails, but they can occur in erroneous programs (e.g. testing for equality // against a non-constant value). - foreach (var state in statesForCases) + whenTrueValues.TryGetValue(test.Input, out IValueSet? whenTrueValuesOpt); + whenFalseValues.TryGetValue(test.Input, out IValueSet? whenFalseValuesOpt); + foreach (var statesForCase in statesForCases) { SplitCase( - state, test, - whenTrueValues.TryGetValue(test.Input, out var v1) ? v1 : null, - whenFalseValues.TryGetValue(test.Input, out var v2) ? v2 : null, + state, statesForCase, test, + whenTrueValuesOpt, whenFalseValuesOpt, out var whenTrueState, out var whenFalseState, ref foundExplicitNullTest); // whenTrueState.IsImpossible occurs when Split results in a state for a given case where the case has been ruled // out (because its test has failed). If not whenTruePossible, we don't want to add anything to the state. In @@ -1041,17 +1043,66 @@ private static ( } IValueSet fromTestPassing = valueFac.Related(relation.Operator(), value); IValueSet fromTestFailing = fromTestPassing.Complement(); - if (values.TryGetValue(test.Input, out IValueSet? tempValuesBeforeTest)) + if (values.TryGetValue(input, out IValueSet? tempValuesBeforeTest)) + { + fromTestPassing = fromTestPassing.Intersect(tempValuesBeforeTest); + fromTestFailing = fromTestFailing.Intersect(tempValuesBeforeTest); + } + // TODO: Set BoundDagPropertyEvaluation.IsLengthOrCount for indexable types + else if (input.Source is BoundDagPropertyEvaluation { IsLengthOrCount: true } e) { + Debug.Assert(input.Type.SpecialType == SpecialType.System_Int32); + (_, BoundDagTemp lengthTemp, int offset) = GetCanonicalInput(e); + if (values.TryGetValue(lengthTemp, out tempValuesBeforeTest)) + { + var lengthValues = ((INumericValueSet)fromTestPassing).Shift(offset); + values = values.SetItem(lengthTemp, lengthValues.Intersect(tempValuesBeforeTest)); + } + + tempValuesBeforeTest = ValueSetFactory.PositiveIntValues; fromTestPassing = fromTestPassing.Intersect(tempValuesBeforeTest); fromTestFailing = fromTestFailing.Intersect(tempValuesBeforeTest); } + var whenTrueValues = values.SetItem(input, fromTestPassing); var whenFalseValues = values.SetItem(input, fromTestFailing); return (whenTrueValues, whenFalseValues, !fromTestPassing.IsEmpty, !fromTestFailing.IsEmpty); } } + private static (BoundDagTemp input, BoundDagTemp lengthTemp, int offset) GetCanonicalInput(BoundDagPropertyEvaluation e) + { + Debug.Assert(e.IsLengthOrCount); + int offset = 0; + BoundDagTemp input = e.Input; + BoundDagTemp lengthTemp = input; + BoundDagEvaluation? source = input.Source; + while (source is BoundDagSliceEvaluation slice) + { + offset += slice.StartIndex - slice.EndIndex; + lengthTemp = slice.LengthTemp; + input = slice.Input; + source = input.Source; + } + return (input, lengthTemp, offset); + } + + private static (BoundDagTemp input, BoundDagTemp lengthTemp, int index) GetCanonicalInput(BoundDagIndexerEvaluation e) + { + int index = e.Index; + BoundDagTemp input = e.Input; + BoundDagTemp lengthTemp = e.LengthTemp; + BoundDagEvaluation? source = input.Source; + while (source is BoundDagSliceEvaluation slice) + { + index = index < 0 ? index - slice.EndIndex : index + slice.StartIndex; + lengthTemp = slice.LengthTemp; + input = slice.Input; + source = input.Source; + } + return (input, lengthTemp, index); + } + private static ImmutableArray RemoveEvaluation(ImmutableArray cases, BoundDagEvaluation e) { var builder = ArrayBuilder.GetInstance(cases.Length); @@ -1087,12 +1138,15 @@ private static ImmutableArray RemoveEvaluation(ImmutableArrayset if a false result on would permit to succeed /// set if being true means has been proven true /// set if being false means has been proven true + /// private void CheckConsistentDecision( BoundDagTest test, BoundDagTest other, + DagState state, IValueSet? whenTrueValues, IValueSet? whenFalseValues, SyntaxNode syntax, + out Tests? trueOtherOpt, out bool trueTestPermitsTrueOther, out bool falseTestPermitsTrueOther, out bool trueTestImpliesTrueOther, @@ -1105,9 +1159,63 @@ private void CheckConsistentDecision( trueTestImpliesTrueOther = false; falseTestImpliesTrueOther = false; + trueOtherOpt = null; + // if the tests are for unrelated things, there is no implication from one to the other if (!test.Input.Equals(other.Input)) - return; + { + switch (test.Input.Source, other.Input.Source) + { + case (BoundDagPropertyEvaluation { IsLengthOrCount: true } s1, BoundDagPropertyEvaluation { IsLengthOrCount: true } s2): + { + (BoundDagTemp s1Input, _, int s1Offset) = GetCanonicalInput(s1); + (BoundDagTemp s2Input, _, int s2Offset) = GetCanonicalInput(s2); + if (s1Input.Equals(s2Input)) + { + Debug.Assert(whenTrueValues is INumericValueSet); + Debug.Assert(whenFalseValues is INumericValueSet); + whenTrueValues = ((INumericValueSet)whenTrueValues).Shift(s1Offset - s2Offset); + whenFalseValues = whenTrueValues.Complement(); + break; + } + } + return; + case (BoundDagIndexerEvaluation s1, BoundDagIndexerEvaluation s2): + { + (BoundDagTemp s1Input, BoundDagTemp s1LengthTemp, int s1Index) = GetCanonicalInput(s1); + (BoundDagTemp s2Input, BoundDagTemp s2LengthTemp, int s2Index) = GetCanonicalInput(s2); + if (s1Input.Equals(s2Input)) + { + Debug.Assert(s1LengthTemp.Equals(s2LengthTemp)); + if (s1Index == s2Index) + { + break; + } + + if (s1Index < 0 != s2Index < 0) + { + var lengthValues = (IValueSet)state.RemainingValues[s1LengthTemp]; + if (!lengthValues.IsEmpty) + { + int lengthValue = s1Index < 0 ? s2Index - s1Index : s1Index - s2Index; + if (lengthValues.All(BinaryOperatorKind.Equal, lengthValue)) + { + break; + } + if (lengthValues.Any(BinaryOperatorKind.Equal, lengthValue)) + { + trueOtherOpt = new Tests.One(new BoundDagValueTest(syntax, ConstantValue.Create(lengthValue), s1LengthTemp)); + break; + } + } + } + } + } + return; + default: + return; + } + } switch (test) { @@ -1176,8 +1284,6 @@ private void CheckConsistentDecision( } } break; - case BoundDagValueTest _: - break; case BoundDagExplicitNullTest _: foundExplicitNullTest = true; // v is T --> !(v == null) @@ -1195,8 +1301,6 @@ private void CheckConsistentDecision( // v == K --> v != null trueTestImpliesTrueOther = true; break; - case BoundDagTypeTest _: - break; case BoundDagExplicitNullTest _: foundExplicitNullTest = true; // v == K --> !(v == null) @@ -1260,6 +1364,7 @@ void handleRelationWithValue( } } + /// /// Determine what we can learn from one successful runtime type test about another planned /// runtime type test for the purpose of building the decision tree. @@ -1491,6 +1596,8 @@ string dumpDagTest(BoundDagTest d) return $"t{tempIdentifier(e)}={e.Kind}({tempName(e.Input)}.{e.Field.Name})"; case BoundDagPropertyEvaluation e: return $"t{tempIdentifier(e)}={e.Kind}({tempName(e.Input)}.{e.Property.Name})"; + case BoundDagIndexerEvaluation e: + return $"t{tempIdentifier(e)}={e.Kind}({tempName(e.Input)}[{e.Index}])"; case BoundDagEvaluation e: return $"t{tempIdentifier(e)}={e.Kind}({tempName(e.Input)})"; case BoundDagTypeTest b: @@ -1683,6 +1790,7 @@ private Tests() { } public abstract void Filter( DecisionDagBuilder builder, BoundDagTest test, + DagState state, IValueSet? whenTrueValues, IValueSet? whenFalseValues, out Tests whenTrue, @@ -1702,6 +1810,7 @@ public sealed class True : Tests public override void Filter( DecisionDagBuilder builder, BoundDagTest test, + DagState state, IValueSet? whenTrueValues, IValueSet? whenFalseValues, out Tests whenTrue, @@ -1722,6 +1831,7 @@ public sealed class False : Tests public override void Filter( DecisionDagBuilder builder, BoundDagTest test, + DagState state, IValueSet? whenTrueValues, IValueSet? whenFalseValues, out Tests whenTrue, @@ -1745,6 +1855,7 @@ public sealed class One : Tests public override void Filter( DecisionDagBuilder builder, BoundDagTest test, + DagState state, IValueSet? whenTrueValues, IValueSet? whenFalseValues, out Tests whenTrue, @@ -1754,16 +1865,18 @@ public override void Filter( builder.CheckConsistentDecision( test: test, other: Test, + state: state, whenTrueValues: whenTrueValues, whenFalseValues: whenFalseValues, syntax: test.Syntax, + trueOtherOpt: out Tests? trueOtherOpt, trueTestPermitsTrueOther: out bool trueDecisionPermitsTrueOther, falseTestPermitsTrueOther: out bool falseDecisionPermitsTrueOther, trueTestImpliesTrueOther: out bool trueDecisionImpliesTrueOther, falseTestImpliesTrueOther: out bool falseDecisionImpliesTrueOther, foundExplicitNullTest: ref foundExplicitNullTest); - whenTrue = trueDecisionImpliesTrueOther ? Tests.True.Instance : trueDecisionPermitsTrueOther ? this : (Tests)Tests.False.Instance; - whenFalse = falseDecisionImpliesTrueOther ? Tests.True.Instance : falseDecisionPermitsTrueOther ? this : (Tests)Tests.False.Instance; + whenTrue = trueDecisionImpliesTrueOther ? trueOtherOpt ?? True.Instance : trueDecisionPermitsTrueOther ? this : False.Instance; + whenFalse = falseDecisionImpliesTrueOther ? trueOtherOpt ?? True.Instance : falseDecisionPermitsTrueOther ? this : False.Instance; } public override BoundDagTest ComputeSelectedTest() => this.Test; public override Tests RemoveEvaluation(BoundDagEvaluation e) => e.Equals(Test) ? Tests.True.Instance : (Tests)this; @@ -1801,13 +1914,14 @@ private static ArrayBuilder NegateSequenceElements(ImmutableArray public override void Filter( DecisionDagBuilder builder, BoundDagTest test, + DagState state, IValueSet? whenTrueValues, IValueSet? whenFalseValues, out Tests whenTrue, out Tests whenFalse, ref bool foundExplicitNullTest) { - Negated.Filter(builder, test, whenTrueValues, whenFalseValues, out var whenTestTrue, out var whenTestFalse, ref foundExplicitNullTest); + Negated.Filter(builder, test, state, whenTrueValues, whenFalseValues, out var whenTestTrue, out var whenTestFalse, ref foundExplicitNullTest); whenTrue = Not.Create(whenTestTrue); whenFalse = Not.Create(whenTestFalse); } @@ -1827,6 +1941,7 @@ protected SequenceTests(ImmutableArray remainingTests) public override void Filter( DecisionDagBuilder builder, BoundDagTest test, + DagState state, IValueSet? whenTrueValues, IValueSet? whenFalseValues, out Tests whenTrue, @@ -1837,7 +1952,7 @@ public override void Filter( var falseBuilder = ArrayBuilder.GetInstance(RemainingTests.Length); foreach (var other in RemainingTests) { - other.Filter(builder, test, whenTrueValues, whenFalseValues, out Tests oneTrue, out Tests oneFalse, ref foundExplicitNullTest); + other.Filter(builder, test, state, whenTrueValues, whenFalseValues, out Tests oneTrue, out Tests oneFalse, ref foundExplicitNullTest); trueBuilder.Add(oneTrue); falseBuilder.Add(oneFalse); } diff --git a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder_ListPatterns.cs b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder_ListPatterns.cs index b1e0d155813f2..0e773ea02628b 100644 --- a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder_ListPatterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder_ListPatterns.cs @@ -40,7 +40,7 @@ private Tests MakeTestsAndBindingsForListPattern(BoundDagTemp input, BoundListPa { Debug.Assert(list.LengthProperty is not null); - var lengthEvaluation = new BoundDagPropertyEvaluation(syntax, list.LengthProperty, input); + var lengthEvaluation = new BoundDagPropertyEvaluation(syntax, list.LengthProperty, isLengthOrCount: true, input); tests.Add(new Tests.One(lengthEvaluation)); var lengthTemp = new BoundDagTemp(syntax, _compilation.GetSpecialType(SpecialType.System_Int32), lengthEvaluation); tests.Add(new Tests.One(list.HasSlice diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 4c1a226a9c7cd..634a44e385a79 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -1429,6 +1429,7 @@ + diff --git a/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs b/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs index 4215d0e944d51..9c1fbaa60d41e 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs @@ -640,6 +640,14 @@ public BoundDagTemp(SyntaxNode syntax, TypeSymbol type, BoundDagEvaluation? sour public static BoundDagTemp ForOriginalInput(BoundExpression expr) => new BoundDagTemp(expr.Syntax, expr.Type!, source: null); } + internal partial class BoundDagPropertyEvaluation + { + public BoundDagPropertyEvaluation(SyntaxNode syntax, PropertySymbol property, BoundDagTemp input, bool hasErrors = false) + : this(syntax, property, isLengthOrCount: false, input, hasErrors) + { + } + } + internal partial class BoundCompoundAssignmentOperator { public BoundCompoundAssignmentOperator(SyntaxNode syntax, diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index ed270329aec1d..57dec5d4b1b0d 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -5107,7 +5107,7 @@ public BoundDagFieldEvaluation Update(FieldSymbol field, BoundDagTemp input) internal sealed partial class BoundDagPropertyEvaluation : BoundDagEvaluation { - public BoundDagPropertyEvaluation(SyntaxNode syntax, PropertySymbol property, BoundDagTemp input, bool hasErrors = false) + public BoundDagPropertyEvaluation(SyntaxNode syntax, PropertySymbol property, bool isLengthOrCount, BoundDagTemp input, bool hasErrors = false) : base(BoundKind.DagPropertyEvaluation, syntax, input, hasErrors || input.HasErrors()) { @@ -5115,18 +5115,21 @@ public BoundDagPropertyEvaluation(SyntaxNode syntax, PropertySymbol property, Bo RoslynDebug.Assert(input is object, "Field 'input' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); this.Property = property; + this.IsLengthOrCount = isLengthOrCount; } public PropertySymbol Property { get; } + + public bool IsLengthOrCount { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitDagPropertyEvaluation(this); - public BoundDagPropertyEvaluation Update(PropertySymbol property, BoundDagTemp input) + public BoundDagPropertyEvaluation Update(PropertySymbol property, bool isLengthOrCount, BoundDagTemp input) { - if (!Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(property, this.Property) || input != this.Input) + if (!Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(property, this.Property) || isLengthOrCount != this.IsLengthOrCount || input != this.Input) { - var result = new BoundDagPropertyEvaluation(this.Syntax, property, input, this.HasErrors); + var result = new BoundDagPropertyEvaluation(this.Syntax, property, isLengthOrCount, input, this.HasErrors); result.CopyAttributes(this); return result; } @@ -10741,7 +10744,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor public override BoundNode? VisitDagPropertyEvaluation(BoundDagPropertyEvaluation node) { BoundDagTemp input = (BoundDagTemp)this.Visit(node.Input); - return node.Update(node.Property, input); + return node.Update(node.Property, node.IsLengthOrCount, input); } public override BoundNode? VisitDagIndexEvaluation(BoundDagIndexEvaluation node) { @@ -12524,7 +12527,7 @@ public NullabilityRewriter(ImmutableDictionary new TreeDumperNode("dagPropertyEvaluation", null, new TreeDumperNode[] { new TreeDumperNode("property", node.Property, null), + new TreeDumperNode("isLengthOrCount", node.IsLengthOrCount, null), new TreeDumperNode("input", null, new TreeDumperNode[] { Visit(node.Input, null) }), new TreeDumperNode("hasErrors", node.HasErrors, null) } diff --git a/src/Compilers/CSharp/Portable/Utilities/IValueSet.cs b/src/Compilers/CSharp/Portable/Utilities/IValueSet.cs index 9be904f167640..d548db3810b99 100644 --- a/src/Compilers/CSharp/Portable/Utilities/IValueSet.cs +++ b/src/Compilers/CSharp/Portable/Utilities/IValueSet.cs @@ -93,4 +93,9 @@ internal interface IValueSet : IValueSet /// bool All(BinaryOperatorKind relation, T value); } + + internal interface INumericValueSet : IValueSet + { + INumericValueSet Shift(int offset); + } } diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSet.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSet.cs index 7eb3cdf90a5f2..ee2c902d620af 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSet.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSet.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -19,7 +20,7 @@ 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 : INumericValueSet where TTC : struct, INumericTC { private readonly ImmutableArray<(T first, T last)> _intervals; @@ -51,6 +52,21 @@ internal NumericValueSet(ImmutableArray<(T first, T last)> intervals) public bool IsEmpty => _intervals.Length == 0; + INumericValueSet INumericValueSet.Shift(int offset) + { + Debug.Assert(this is NumericValueSet); + if (offset == 0) + return this; + var builder = ArrayBuilder<(int first, int last)>.GetInstance(); + foreach (var (first, last) in Unsafe.As>(this)._intervals) + { + builder.Add(( + first == int.MaxValue ? first : first + offset, + last == int.MaxValue ? last : last + offset)); + } + return Unsafe.As>(new NumericValueSet(builder.ToImmutableAndFree())); + } + ConstantValue IValueSet.Sample { get diff --git a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.cs b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.cs index 07b2d867e7f58..0d8674d152d21 100644 --- a/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.cs +++ b/src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.cs @@ -28,6 +28,8 @@ internal static partial class ValueSetFactory internal static readonly IValueSetFactory ForNint = NintValueSetFactory.Instance; internal static readonly IValueSetFactory ForNuint = NuintValueSetFactory.Instance; + internal static readonly IValueSet PositiveIntValues = new NumericValueSet(0, int.MaxValue); + public static IValueSetFactory? ForSpecialType(SpecialType specialType, bool isNative = false) { switch (specialType) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests_ListPatterns.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests_ListPatterns.cs index 50a603dd81438..abbdfd7e420f0 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests_ListPatterns.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests_ListPatterns.cs @@ -74,7 +74,7 @@ public static void Main() AssertEx.Multiple( () => verifier.VerifyIL("X.Test(System.Span)", @" { - // Code size 76 (0x4c) + // Code size 69 (0x45) .maxstack 4 .locals init (char V_0, //first System.Span V_1, //others @@ -87,47 +87,43 @@ .locals init (char V_0, //first IL_0004: call ""int System.Span.Length.get"" IL_0009: stloc.s V_4 IL_000b: ldloc.s V_4 - IL_000d: ldc.i4.2 - IL_000e: bge.s IL_0017 - IL_0010: ldloc.s V_4 - IL_0012: ldc.i4.1 - IL_0013: ble.un.s IL_003d - IL_0015: br.s IL_004a - IL_0017: ldloca.s V_3 - IL_0019: ldc.i4.0 - IL_001a: call ""ref char System.Span.this[int].get"" - IL_001f: ldind.u2 - IL_0020: stloc.0 - IL_0021: ldloca.s V_3 - IL_0023: ldc.i4.1 - IL_0024: ldloc.s V_4 - IL_0026: ldc.i4.2 - IL_0027: sub - IL_0028: call ""System.Span System.Span.Slice(int, int)"" - IL_002d: stloc.1 - IL_002e: ldloca.s V_3 - IL_0030: ldloc.s V_4 - IL_0032: ldc.i4.1 - IL_0033: sub - IL_0034: call ""ref char System.Span.this[int].get"" - IL_0039: ldind.u2 - IL_003a: stloc.2 - IL_003b: br.s IL_003f - IL_003d: ldc.i4.1 - IL_003e: ret - IL_003f: ldloc.0 - IL_0040: ldloc.2 - IL_0041: bne.un.s IL_004a - IL_0043: ldloc.1 - IL_0044: call ""bool X.Test(System.Span)"" - IL_0049: ret - IL_004a: ldc.i4.0 - IL_004b: ret + IL_000d: ldc.i4.1 + IL_000e: ble.un.s IL_0036 + IL_0010: ldloca.s V_3 + IL_0012: ldc.i4.0 + IL_0013: call ""ref char System.Span.this[int].get"" + IL_0018: ldind.u2 + IL_0019: stloc.0 + IL_001a: ldloca.s V_3 + IL_001c: ldc.i4.1 + IL_001d: ldloc.s V_4 + IL_001f: ldc.i4.2 + IL_0020: sub + IL_0021: call ""System.Span System.Span.Slice(int, int)"" + IL_0026: stloc.1 + IL_0027: ldloca.s V_3 + IL_0029: ldloc.s V_4 + IL_002b: ldc.i4.1 + IL_002c: sub + IL_002d: call ""ref char System.Span.this[int].get"" + IL_0032: ldind.u2 + IL_0033: stloc.2 + IL_0034: br.s IL_0038 + IL_0036: ldc.i4.1 + IL_0037: ret + IL_0038: ldloc.0 + IL_0039: ldloc.2 + IL_003a: bne.un.s IL_0043 + IL_003c: ldloc.1 + IL_003d: call ""bool X.Test(System.Span)"" + IL_0042: ret + IL_0043: ldc.i4.0 + IL_0044: ret } "), () => verifier.VerifyIL("X.Test(char[])", @" { - // Code size 79 (0x4f) + // Code size 72 (0x48) .maxstack 4 .locals init (char V_0, //first char[] V_1, //others @@ -137,53 +133,49 @@ .locals init (char V_0, //first IL_0000: ldarg.0 IL_0001: stloc.3 IL_0002: ldloc.3 - IL_0003: brfalse.s IL_004d + IL_0003: brfalse.s IL_0046 IL_0005: ldloc.3 IL_0006: callvirt ""int System.Array.Length.get"" IL_000b: stloc.s V_4 IL_000d: ldloc.s V_4 - IL_000f: ldc.i4.2 - IL_0010: bge.s IL_0019 - IL_0012: ldloc.s V_4 - IL_0014: ldc.i4.1 - IL_0015: ble.un.s IL_0040 - IL_0017: br.s IL_004d - IL_0019: ldloc.3 - IL_001a: ldc.i4.0 - IL_001b: ldelem.u2 - IL_001c: stloc.0 - IL_001d: ldloc.3 + IL_000f: ldc.i4.1 + IL_0010: ble.un.s IL_0039 + IL_0012: ldloc.3 + IL_0013: ldc.i4.0 + IL_0014: ldelem.u2 + IL_0015: stloc.0 + IL_0016: ldloc.3 + IL_0017: ldc.i4.1 + IL_0018: ldc.i4.0 + IL_0019: newobj ""System.Index..ctor(int, bool)"" IL_001e: ldc.i4.1 - IL_001f: ldc.i4.0 + IL_001f: ldc.i4.1 IL_0020: newobj ""System.Index..ctor(int, bool)"" - IL_0025: ldc.i4.1 - IL_0026: ldc.i4.1 - IL_0027: newobj ""System.Index..ctor(int, bool)"" - IL_002c: newobj ""System.Range..ctor(System.Index, System.Index)"" - IL_0031: call ""char[] System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray(char[], System.Range)"" - IL_0036: stloc.1 - IL_0037: ldloc.3 - IL_0038: ldloc.s V_4 - IL_003a: ldc.i4.1 - IL_003b: sub - IL_003c: ldelem.u2 - IL_003d: stloc.2 - IL_003e: br.s IL_0042 - IL_0040: ldc.i4.1 - IL_0041: ret - IL_0042: ldloc.0 - IL_0043: ldloc.2 - IL_0044: bne.un.s IL_004d - IL_0046: ldloc.1 - IL_0047: call ""bool X.Test(char[])"" - IL_004c: ret - IL_004d: ldc.i4.0 - IL_004e: ret + IL_0025: newobj ""System.Range..ctor(System.Index, System.Index)"" + IL_002a: call ""char[] System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray(char[], System.Range)"" + IL_002f: stloc.1 + IL_0030: ldloc.3 + IL_0031: ldloc.s V_4 + IL_0033: ldc.i4.1 + IL_0034: sub + IL_0035: ldelem.u2 + IL_0036: stloc.2 + IL_0037: br.s IL_003b + IL_0039: ldc.i4.1 + IL_003a: ret + IL_003b: ldloc.0 + IL_003c: ldloc.2 + IL_003d: bne.un.s IL_0046 + IL_003f: ldloc.1 + IL_0040: call ""bool X.Test(char[])"" + IL_0045: ret + IL_0046: ldc.i4.0 + IL_0047: ret } "), () => verifier.VerifyIL("X.Test(string)", @" { - // Code size 73 (0x49) + // Code size 66 (0x42) .maxstack 4 .locals init (char V_0, //first string V_1, //others @@ -193,45 +185,41 @@ .locals init (char V_0, //first IL_0000: ldarg.0 IL_0001: stloc.3 IL_0002: ldloc.3 - IL_0003: brfalse.s IL_0047 + IL_0003: brfalse.s IL_0040 IL_0005: ldloc.3 IL_0006: callvirt ""int string.Length.get"" IL_000b: stloc.s V_4 IL_000d: ldloc.s V_4 - IL_000f: ldc.i4.2 - IL_0010: bge.s IL_0019 - IL_0012: ldloc.s V_4 - IL_0014: ldc.i4.1 - IL_0015: ble.un.s IL_003a - IL_0017: br.s IL_0047 - IL_0019: ldloc.3 - IL_001a: ldc.i4.0 - IL_001b: callvirt ""char string.this[int].get"" - IL_0020: stloc.0 - IL_0021: ldloc.3 - IL_0022: ldc.i4.1 - IL_0023: ldloc.s V_4 - IL_0025: ldc.i4.2 - IL_0026: sub - IL_0027: callvirt ""string string.Substring(int, int)"" - IL_002c: stloc.1 - IL_002d: ldloc.3 - IL_002e: ldloc.s V_4 - IL_0030: ldc.i4.1 - IL_0031: sub - IL_0032: callvirt ""char string.this[int].get"" - IL_0037: stloc.2 - IL_0038: br.s IL_003c - IL_003a: ldc.i4.1 - IL_003b: ret - IL_003c: ldloc.0 - IL_003d: ldloc.2 - IL_003e: bne.un.s IL_0047 - IL_0040: ldloc.1 - IL_0041: call ""bool X.Test(string)"" - IL_0046: ret - IL_0047: ldc.i4.0 - IL_0048: ret + IL_000f: ldc.i4.1 + IL_0010: ble.un.s IL_0033 + IL_0012: ldloc.3 + IL_0013: ldc.i4.0 + IL_0014: callvirt ""char string.this[int].get"" + IL_0019: stloc.0 + IL_001a: ldloc.3 + IL_001b: ldc.i4.1 + IL_001c: ldloc.s V_4 + IL_001e: ldc.i4.2 + IL_001f: sub + IL_0020: callvirt ""string string.Substring(int, int)"" + IL_0025: stloc.1 + IL_0026: ldloc.3 + IL_0027: ldloc.s V_4 + IL_0029: ldc.i4.1 + IL_002a: sub + IL_002b: callvirt ""char string.this[int].get"" + IL_0030: stloc.2 + IL_0031: br.s IL_0035 + IL_0033: ldc.i4.1 + IL_0034: ret + IL_0035: ldloc.0 + IL_0036: ldloc.2 + IL_0037: bne.un.s IL_0040 + IL_0039: ldloc.1 + IL_003a: call ""bool X.Test(string)"" + IL_003f: ret + IL_0040: ldc.i4.0 + IL_0041: ret } ") ); @@ -1894,5 +1882,232 @@ readonly void M(Index i, Range r) // _ = this[r]; // 3, 4 Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "this").WithArguments("S.Slice(int, int)", "this").WithLocation(12, 13)); } + + [Fact] + public void Subsumption_01() + { + var src = @" +class C +{ + void Test(int[] a) + { + switch (a) + { + case [..,42]: + case [42]: + break; + } + } +}"; + var comp = CreateCompilationWithIndexAndRange(src, parseOptions: TestOptions.RegularWithListPatterns); + comp.VerifyEmitDiagnostics( + // (9,18): error CS8120: The switch case is unreachable. It has already been handled by a previous case or it is impossible to match. + // case [42]: + Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, "[42]").WithLocation(9, 18) + ); + } + + [Fact] + public void Subsumption_02() + { + var src = @" +class C +{ + void Test(int[] a, int[] b) + { + switch (a, b) + { + case ([.., 42], [.., 43]): + case ([42], [43]): + break; + } + } +}"; + var comp = CreateCompilationWithIndexAndRange(src, parseOptions: TestOptions.RegularWithListPatterns); + comp.VerifyEmitDiagnostics( + // (9,18): error CS8120: The switch case is unreachable. It has already been handled by a previous case or it is impossible to match. + // case ([42], [43]): + Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, "([42], [43])").WithLocation(9, 18)); + } + + [Fact] + public void Subsumption_03() + { + var src = @" +class C +{ + void Test(int[] a) + { + switch (a) + { + case { Length: 1 } and [.., 1]: + case { Length: 1 } and [1, ..]: + break; + } + switch (a) + { + case { Length: 1 } and [1, ..]: + case { Length: 1 } and [.., 1]: + break; + } + } +}"; + var comp = CreateCompilationWithIndexAndRange(src, parseOptions: TestOptions.RegularWithListPatterns); + comp.VerifyEmitDiagnostics( + // (9,18): error CS8120: The switch case is unreachable. It has already been handled by a previous case or it is impossible to match. + // case { Length: 1 } and [1, ..]: + Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, "{ Length: 1 } and [1, ..]").WithLocation(9, 18), + // (15,18): error CS8120: The switch case is unreachable. It has already been handled by a previous case or it is impossible to match. + // case { Length: 1 } and [.., 1]: + Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, "{ Length: 1 } and [.., 1]").WithLocation(15, 18) + ); + } + + [Fact] + public void Subsumption_04() + { + var src = @" +class C +{ + void Test(int[] a) + { + switch (a) + { + case [1, .., 3]: + case [1, 2, 3]: + break; + } + } +}"; + var comp = CreateCompilationWithIndexAndRange(src, parseOptions: TestOptions.RegularWithListPatterns); + comp.VerifyEmitDiagnostics( + // (9,18): error CS8120: The switch case is unreachable. It has already been handled by a previous case or it is impossible to match. + // case [1, 2, 3]: + Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, "[1, 2, 3]").WithLocation(9, 18) + ); + } + + [Fact] + public void Subsumption_05() + { + var src = @" +class C +{ + void Test(int[] a) + { + switch (a) + { + case [1, 2, 3]: + case [1, .., 3]: + break; + } + } +}"; + var comp = CreateCompilationWithIndexAndRange(src, parseOptions: TestOptions.RegularWithListPatterns); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void Subsumption_06() + { + var src = @" +class C +{ + void Test(int[] a) + { + switch (a) + { + case [42]: + case [..,42]: + break; + } + } +}"; + var comp = CreateCompilationWithIndexAndRange(src, parseOptions: TestOptions.RegularWithListPatterns); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void Subsumption_07() + { + var src = @" +class C +{ + void Test(int[] a) + { + switch (a) + { + case [>0, ..]: + case [.., <=0]: + case [var unreachable]: + break; + } + } +}"; + var comp = CreateCompilationWithIndexAndRange(src, parseOptions: TestOptions.RegularWithListPatterns); + comp.VerifyEmitDiagnostics( + // (10,18): error CS8120: The switch case is unreachable. It has already been handled by a previous case or it is impossible to match. + // case [var unreachable]: + Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, "[var unreachable]").WithLocation(10, 18)); + } + + [Theory] + [CombinatorialData] + public void Subsumption_Slice_00( + [CombinatorialValues( + "[1,2,3]", + "[1,2,3,..[]]", + "[1,2,..[],3]", + "[1,..[],2,3]", + "[..[],1,2,3]", + "[1,..[2,3]]", + "[..[1,2],3]", + "[..[1,2,3]]", + "[..[..[1,2,3]]]", + "[..[1,2,3,..[]]]", + "[..[1,2,..[],3]]", + "[..[1,..[],2,3]]", + "[..[..[],1,2,3]]", + "[..[1,..[2,3]]]", + "[..[..[1,2],3]]")] + string case1, + [CombinatorialValues( + "[1,2,3]", + "[1,2,3,..[]]", + "[1,2,..[],3]", + "[1,..[],2,3]", + "[..[],1,2,3]", + "[1,..[2,3]]", + "[..[1,2],3]", + "[..[1,2,3]]", + "[..[..[1,2,3]]]", + "[..[1,2,3,..[]]]", + "[..[1,2,..[],3]]", + "[..[1,..[],2,3]]", + "[..[..[],1,2,3]]", + "[..[1,..[2,3]]]", + "[..[..[1,2],3]]")] + string case2) + { + var src = @" +using System; +class C +{ + void Test(Span a) + { + switch (a) + { + case " + case1 + @": + case " + case2 + @": + break; + } + } +}"; + var comp = CreateCompilationWithIndexAndRangeAndSpan(src, parseOptions: TestOptions.RegularWithListPatterns); + comp.VerifyEmitDiagnostics( + // (10,18): error CS8120: The switch case is unreachable. It has already been handled by a previous case or it is impossible to match. + Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, case2).WithLocation(10, 18) + ); + } } }