diff --git a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs index 1e60285339857..839f9b73f88e6 100644 --- a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs +++ b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs @@ -421,8 +421,12 @@ private static void MakeCheckNotNull( ArrayBuilder tests) { // Add a null test if needed - if (input.Type.CanContainNull()) + if (input.Type.CanContainNull() && + // The slice value is assumed to be never null + input.Source is not BoundDagSliceEvaluation) + { tests.Add(new Tests.One(new BoundDagNonNullTest(syntax, isExplicitTest, input))); + } } /// diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs index bf798cf0a94a6..79813f67b08e5 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs @@ -514,6 +514,7 @@ public PossiblyConditionalState Clone() var outputSlot = makeDagTempSlot(type, output); Debug.Assert(outputSlot > 0); addToTempMap(output, outputSlot, type.Type); + this.State[outputSlot] = NullableFlowState.NotNull; // Slice value is assumed to be never null break; } case BoundDagAssignmentEvaluation e: diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTestBase.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTestBase.cs index 1123370d1d3ed..ed9c5eafff40b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTestBase.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTestBase.cs @@ -441,12 +441,12 @@ protected static void AssertEmpty(SymbolInfo info) Assert.Equal(CandidateReason.None, info.CandidateReason); } - protected static void VerifyDecisionDagDump(Compilation comp, string expectedDecisionDag) + protected static void VerifyDecisionDagDump(Compilation comp, string expectedDecisionDag, int index = 0) where T : CSharpSyntaxNode { #if DEBUG var tree = comp.SyntaxTrees.First(); - var node = tree.GetRoot().DescendantNodes().OfType().First(); + var node = tree.GetRoot().DescendantNodes().OfType().ElementAt(index); var model = (CSharpSemanticModel)comp.GetSemanticModel(tree); var binder = model.GetEnclosingBinder(node.SpanStart); var decisionDag = node switch diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests_ListPatterns.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests_ListPatterns.cs index 3a2f80d06c143..0ded798122769 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests_ListPatterns.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests_ListPatterns.cs @@ -1381,6 +1381,33 @@ public void M(int[] a) ); } + [Fact] + public void SlicePattern_NullValue() + { + var source = @" +#nullable enable +class C +{ + public int Length => 3; + public int this[int i] => 0; + public int[]? Slice(int i, int j) => null; + + public static void Main() + { + if (new C() is [.. var s0] && s0 == null) + System.Console.Write(1); + if (new C() is [.. null]) + System.Console.Write(2); + if (new C() is not [.. {}]) + System.Console.Write(3); + } +} +"; + var compilation = CreateCompilationWithIndexAndRange(source, options: TestOptions.ReleaseExe); + compilation.VerifyEmitDiagnostics(); + CompileAndVerify(compilation, expectedOutput: "12"); + } + [Fact] public void ListPattern_MemberLookup_StaticIndexer() { @@ -3853,14 +3880,14 @@ public void M() rest.ToString(); // 1 if (new C() is [1, ..var rest2]) - rest2.Value.ToString(); // 2 + rest2.Value.ToString(); // (assumed not-null) else - rest2.Value.ToString(); // 3, 4 + rest2.Value.ToString(); // 2, 3 if (new C() is [1, ..var rest3]) - rest3.ToString(); // 5 + rest3.ToString(); // (assumed not-null) else - rest3.ToString(); // 6, 7 + rest3.ToString(); // 4, 5 if (new C() is [1, ..var rest4]) { @@ -3868,11 +3895,11 @@ public void M() rest4 = null; } else - rest4.ToString(); // 8, 9 + rest4.ToString(); // 6, 7 if (new C() is [1, ..var rest5]) { - rest5.ToString(); // 10 + rest5.ToString(); // (assumed not-null) rest5 = default; } } @@ -3883,33 +3910,24 @@ public void M() // (14,13): error CS0165: Use of unassigned local variable 'rest' // rest.ToString(); // 1 Diagnostic(ErrorCode.ERR_UseDefViolation, "rest").WithArguments("rest").WithLocation(14, 13), - // (17,13): warning CS8629: Nullable value type may be null. - // rest2.Value.ToString(); // 2 - Diagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, "rest2").WithLocation(17, 13), // (19,13): warning CS8629: Nullable value type may be null. - // rest2.Value.ToString(); // 3, 4 + // rest2.Value.ToString(); // 2, 3 Diagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, "rest2").WithLocation(19, 13), // (19,13): error CS0165: Use of unassigned local variable 'rest2' - // rest2.Value.ToString(); // 3, 4 + // rest2.Value.ToString(); // 2, 3 Diagnostic(ErrorCode.ERR_UseDefViolation, "rest2").WithArguments("rest2").WithLocation(19, 13), - // (22,13): warning CS8602: Dereference of a possibly null reference. - // rest3.ToString(); // 5 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "rest3").WithLocation(22, 13), // (24,13): warning CS8602: Dereference of a possibly null reference. - // rest3.ToString(); // 6, 7 + // rest3.ToString(); // 4, 5 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "rest3").WithLocation(24, 13), // (24,13): error CS0165: Use of unassigned local variable 'rest3' - // rest3.ToString(); // 6, 7 + // rest3.ToString(); // 4, 5 Diagnostic(ErrorCode.ERR_UseDefViolation, "rest3").WithArguments("rest3").WithLocation(24, 13), // (32,13): warning CS8602: Dereference of a possibly null reference. - // rest4.ToString(); // 8, 9 + // rest4.ToString(); // 6, 7 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "rest4").WithLocation(32, 13), // (32,13): error CS0165: Use of unassigned local variable 'rest4' - // rest4.ToString(); // 8, 9 - Diagnostic(ErrorCode.ERR_UseDefViolation, "rest4").WithArguments("rest4").WithLocation(32, 13), - // (36,13): warning CS8602: Dereference of a possibly null reference. - // rest5.ToString(); // 10 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "rest5").WithLocation(36, 13) + // rest4.ToString(); // 6, 7 + Diagnostic(ErrorCode.ERR_UseDefViolation, "rest4").WithArguments("rest4").WithLocation(32, 13) ); var tree = compilation.SyntaxTrees.First(); @@ -3931,6 +3949,53 @@ void verify(VarPatternSyntax declaration, string name, string expectedType) } } + [Fact] + public void SlicePattern_Nullability_Annotation() + { + var source = @" +#nullable enable +class C +{ + public int Length => throw null!; + public int this[int i] => throw null!; + public int[]? Slice(int i, int j) => throw null!; + + public void M() + { + if (this is [1, ..var slice]) + slice.ToString(); + if (this is [1, ..[] list]) + list.ToString(); + } +} +"; + var compilation = CreateCompilationWithIndexAndRange(source); + compilation.VerifyEmitDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree, ignoreAccessibility: false); + var nodes = tree.GetRoot().DescendantNodes().OfType(); + Assert.Collection(nodes, + d => verify(d, "slice", "int[]?", "int[]"), + d => verify(d, "list", "int[]?", "int[]") + ); + + void verify(SyntaxNode designation, string syntax, string declaredType, string type) + { + Assert.Equal(syntax, designation.ToString()); + var model = compilation.GetSemanticModel(tree); + var symbol = model.GetDeclaredSymbol(designation); + Assert.Equal(SymbolKind.Local, symbol.Kind); + Assert.Equal(declaredType, ((ILocalSymbol)symbol).Type.ToDisplayString()); + var typeInfo = model.GetTypeInfo(designation); + Assert.Null(typeInfo.Type); + Assert.Null(typeInfo.ConvertedType); + typeInfo = model.GetTypeInfo(designation.Parent); + Assert.Equal(type, typeInfo.Type.ToDisplayString()); + Assert.Equal(type, typeInfo.ConvertedType.ToDisplayString()); + } + } + [Fact] public void SlicePattern_Nullability_RangeIndexer() { @@ -3951,14 +4016,14 @@ public void M() rest.ToString(); // 1 if (new C() is [1, ..var rest2]) - rest2.Value.ToString(); // 2 + rest2.Value.ToString(); // (assumed not-null) else - rest2.Value.ToString(); // 3, 4 + rest2.Value.ToString(); // 2, 3 if (new C() is [1, ..var rest3]) - rest3.ToString(); // 5 + rest3.ToString(); // (assumed not-null) else - rest3.ToString(); // 6, 7 + rest3.ToString(); // 4, 5 if (new C() is [1, ..var rest4]) { @@ -3966,7 +4031,7 @@ public void M() rest4 = null; } else - rest4.ToString(); // 8, 9 + rest4.ToString(); // 6, 7 } } "; @@ -3975,29 +4040,23 @@ public void M() // (15,13): error CS0165: Use of unassigned local variable 'rest' // rest.ToString(); // 1 Diagnostic(ErrorCode.ERR_UseDefViolation, "rest").WithArguments("rest").WithLocation(15, 13), - // (18,13): warning CS8629: Nullable value type may be null. - // rest2.Value.ToString(); // 2 - Diagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, "rest2").WithLocation(18, 13), // (20,13): warning CS8629: Nullable value type may be null. - // rest2.Value.ToString(); // 3, 4 + // rest2.Value.ToString(); // 2, 3 Diagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, "rest2").WithLocation(20, 13), // (20,13): error CS0165: Use of unassigned local variable 'rest2' - // rest2.Value.ToString(); // 3, 4 + // rest2.Value.ToString(); // 2, 3 Diagnostic(ErrorCode.ERR_UseDefViolation, "rest2").WithArguments("rest2").WithLocation(20, 13), - // (23,13): warning CS8602: Dereference of a possibly null reference. - // rest3.ToString(); // 5 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "rest3").WithLocation(23, 13), // (25,13): warning CS8602: Dereference of a possibly null reference. - // rest3.ToString(); // 6, 7 + // rest3.ToString(); // 4, 5 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "rest3").WithLocation(25, 13), // (25,13): error CS0165: Use of unassigned local variable 'rest3' - // rest3.ToString(); // 6, 7 + // rest3.ToString(); // 4, 5 Diagnostic(ErrorCode.ERR_UseDefViolation, "rest3").WithArguments("rest3").WithLocation(25, 13), // (33,13): warning CS8602: Dereference of a possibly null reference. - // rest4.ToString(); // 8, 9 + // rest4.ToString(); // 6, 7 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "rest4").WithLocation(33, 13), // (33,13): error CS0165: Use of unassigned local variable 'rest4' - // rest4.ToString(); // 8, 9 + // rest4.ToString(); // 6, 7 Diagnostic(ErrorCode.ERR_UseDefViolation, "rest4").WithArguments("rest4").WithLocation(33, 13) ); } @@ -4696,63 +4755,63 @@ public void M() [not null] => 0, }; - _ = this switch // we didn't test for [.. null] but we're looking for an example with Length=1. // 1 + _ = this switch // didn't test for [.. null] but the slice is assumed not-null { null or { Length: not 1 } => 0, [.. [null]] => 0, [not null] => 0, }; - _ = this switch // didn't test for [.. [not null]] // // 2 + _ = this switch // didn't test for [.. [not null]] // 1 { null or { Length: not 1 } => 0, [.. [null]] => 0, }; - _ = this switch // didn't test for [.. [not null]] // // 3 + _ = this switch // didn't test for [.. [not null]] // 2 { null or { Length: not 1 } => 0, [.. null] => 0, [.. [null]] => 0, }; - _ = this switch // didn't test for [.. null, _] // we're trying to construct an example with Length=1, the slice may not be null // 4 + _ = this switch // didn't test for [.. null, _] // we're trying to construct an example with Length=1, the slice may not be null // 3 { null or { Length: not 1 } => 0, [.. [not null]] => 0, }; - _ = this switch // didn't test for [_, .. null, _, _, _] // we're trying to construct an example with Length=4, the slice may not be null // 5 + _ = this switch // didn't test for [_, .. null, _, _, _] // we're trying to construct an example with Length=4, the slice may not be null // 4 { null or { Length: not 4 } => 0, [_, .. [_, not null], _] => 0, }; - _ = this switch // we should consider this switch exhaustive // 6 + _ = this switch // exhaustive { null or { Length: not 4 } => 0, [_, .. [_, _], _] => 0, }; - _ = this switch // didn't test for [_, .. [_, null], _] // 7 + _ = this switch // didn't test for [_, .. [_, null], _] // 5 { null or { Length: not 4 } => 0, [_, .. null or [_, not null], _] => 0, }; - _ = this switch // didn't test for [_, .. [_, null], _, _] // 8 + _ = this switch // didn't test for [_, .. [_, null], _, _] // 6 { null or { Length: not 5 } => 0, [_, .. null or [_, not null], _, _] => 0, }; - _ = this switch // didn't test for [_, .. [_, null, _], _] // 9 + _ = this switch // didn't test for [_, .. [_, null, _], _] // 7 { null or { Length: not 5 } => 0, [_, .. null or [_, not null, _], _] => 0, }; - _ = this switch // didn't test for [.. null, _] // we're trying to construct an example with Length=1 but a null slice // 10 + _ = this switch // didn't test for [.. null, _] but the slice is assumed not-null { null or { Length: not 1 } => 0, [.. { Length: 1 }] => 0, @@ -4763,36 +4822,27 @@ public void M() // Note: we don't try to explain nested slice patterns right now so all these just produce a fallback example var compilation = CreateCompilation(new[] { source, TestSources.Index, TestSources.Range }); compilation.VerifyEmitDiagnostics( - // (20,18): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '_' is not covered. - // _ = this switch // we didn't test for [.. null] but we're looking for an example with Length=1. // 1 - Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("_").WithLocation(20, 18), - // (27,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. - // _ = this switch // didn't test for [.. [not null]] // // 2 - Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("_").WithLocation(27, 18), - // (33,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. - // _ = this switch // didn't test for [.. [not null]] // // 3 - Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("_").WithLocation(33, 18), - // (40,18): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '_' is not covered. - // _ = this switch // didn't test for [.. null, _] // we're trying to construct an example with Length=1, the slice may not be null // 4 - Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("_").WithLocation(40, 18), - // (46,18): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '_' is not covered. - // _ = this switch // didn't test for [_, .. null, _, _, _] // we're trying to construct an example with Length=4, the slice may not be null // 5 - Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("_").WithLocation(46, 18), - // (52,18): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '_' is not covered. - // _ = this switch // we should consider this switch exhaustive // 6 - Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("_").WithLocation(52, 18), - // (58,18): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '_' is not covered. - // _ = this switch // didn't test for [_, .. [_, null], _] // 7 - Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("_").WithLocation(58, 18), - // (64,18): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '_' is not covered. - // _ = this switch // didn't test for [_, .. [_, null], _, _] // 8 - Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("_").WithLocation(64, 18), - // (70,18): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '_' is not covered. - // _ = this switch // didn't test for [_, .. [_, null, _], _] // 9 - Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("_").WithLocation(70, 18), - // (76,18): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '_' is not covered. - // _ = this switch // didn't test for [.. null, _] // we're trying to construct an example with Length=1 but a null slice // 10 - Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("_").WithLocation(76, 18) + // (27,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. + // _ = this switch // didn't test for [.. [not null]] // 1 + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("_").WithLocation(27, 18), + // (33,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. + // _ = this switch // didn't test for [.. [not null]] // 2 + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("_").WithLocation(33, 18), + // (40,18): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '_' is not covered. + // _ = this switch // didn't test for [.. null, _] // we're trying to construct an example with Length=1, the slice may not be null // 3 + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("_").WithLocation(40, 18), + // (46,18): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '_' is not covered. + // _ = this switch // didn't test for [_, .. null, _, _, _] // we're trying to construct an example with Length=4, the slice may not be null // 4 + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("_").WithLocation(46, 18), + // (58,18): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '_' is not covered. + // _ = this switch // didn't test for [_, .. [_, null], _] // 5 + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("_").WithLocation(58, 18), + // (64,18): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '_' is not covered. + // _ = this switch // didn't test for [_, .. [_, null], _, _] // 6 + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("_").WithLocation(64, 18), + // (70,18): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '_' is not covered. + // _ = this switch // didn't test for [_, .. [_, null, _], _] // 7 + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("_").WithLocation(70, 18) ); } @@ -6178,45 +6228,70 @@ void Test(int[] a) { Length: not 1 } => 0, [<0, ..] => 0, [..[>= 0]] or [..null] => 1, - [_] => 2, // unreachable + [_] => 2, // unreachable 1 }; - _ = a switch // exhaustive + _ = a switch { { Length: not 1 } => 0, [<0, ..] => 0, [..[>= 0]] => 1, - [_] => 2, + [_] => 2, // unreachable 2 }; } }" + TestSources.GetSubArray; var comp = CreateCompilationWithIndexAndRange(src); comp.VerifyEmitDiagnostics( - // (11,13): 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. - // [_] => 2, // unreachable - Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "[_]").WithLocation(11, 13)); - - VerifyDecisionDagDump(comp, + // (11,13): 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. + // [_] => 2, // unreachable 1 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "[_]").WithLocation(11, 13), + // (18,13): 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. + // [_] => 2, // unreachable 2 + Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "[_]").WithLocation(18, 13) + ); -@"[0]: t0 != null ? [1] : [12] + AssertEx.Multiple( + () => VerifyDecisionDagDump(comp, +@"[0]: t0 != null ? [1] : [11] [1]: t1 = t0.Length; [2] -[2]: t1 == 1 ? [3] : [11] +[2]: t1 == 1 ? [3] : [10] [3]: t2 = t0[0]; [4] [4]: t2 < 0 ? [5] : [6] [5]: leaf `[<0, ..] => 0` [6]: t3 = DagSliceEvaluation(t0); [7] -[7]: t3 != null ? [8] : [10] -[8]: t4 = t3.Length; [9] -[9]: t5 = t3[0]; [10] -[10]: leaf `[..[>= 0]] or [..null] => 1` -[11]: leaf `{ Length: not 1 } => 0` -[12]: leaf `a switch +[7]: t4 = t3.Length; [8] +[8]: t5 = t3[0]; [9] +[9]: leaf `[..[>= 0]] or [..null] => 1` +[10]: leaf `{ Length: not 1 } => 0` +[11]: leaf `a switch { { Length: not 1 } => 0, [<0, ..] => 0, [..[>= 0]] or [..null] => 1, - [_] => 2, // unreachable + [_] => 2, // unreachable 1 }` -"); +", index: 0), + + () => VerifyDecisionDagDump(comp, +@"[0]: t0 != null ? [1] : [11] +[1]: t1 = t0.Length; [2] +[2]: t1 == 1 ? [3] : [10] +[3]: t2 = t0[0]; [4] +[4]: t2 < 0 ? [5] : [6] +[5]: leaf `[<0, ..] => 0` +[6]: t3 = DagSliceEvaluation(t0); [7] +[7]: t4 = t3.Length; [8] +[8]: t5 = t3[0]; [9] +[9]: leaf `[..[>= 0]] => 1` +[10]: leaf `{ Length: not 1 } => 0` +[11]: leaf `a switch + { + { Length: not 1 } => 0, + [<0, ..] => 0, + [..[>= 0]] => 1, + [_] => 2, // unreachable 2 + }` +", index: 1) + ); } [Fact] @@ -6662,55 +6737,45 @@ void Test(int[] a) "); } - [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]]", - "[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]]", - "[1, ..[2], 3]", - "[1, ..[2, ..[3]]]", - "[1, ..[2, ..[], 3]]")] - string case2) + [Fact] + public void Subsumption_Slice_00() { - var src = @" -using System; + const int Count = 18; + var cases = new string[Count] + { + "[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]]", + "[1, ..[2], 3]", + "[1, ..[2, ..[3]]]", + "[1, ..[2, ..[], 3]]" + }; + + // testing every possible combination takes too long, + // covering a random subset instead. + var r = new Random(); + for (int i = 0; i < 50; i++) + { + var case1 = cases[r.Next(Count)]; + var case2 = cases[r.Next(Count)]; + var type = r.Next(2) == 0 ? "System.Span" : "int[]"; + + var src = @" class C { - void Test(Span a) + void Test(" + type + @" a) { switch (a) { @@ -6720,11 +6785,12 @@ void Test(Span a) } } }"; - 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) - ); + var comp = CreateCompilationWithIndexAndRangeAndSpan(new[] { src, TestSources.GetSubArray }, 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. + Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, case2).WithLocation(9, 18) + ); + } } [Fact] @@ -6749,6 +6815,73 @@ public static void Test(System.Span a) Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, "[..[var v]]").WithLocation(9, 18)); } + [Fact] + public void Subsumption_Slice_02() + { + var source = @" +using System; + +IOuter outer = null; +switch (outer) +{ + case [..[..[10],20]]: + break; + case [..[10],20]: // 1 + break; +} + +interface IOuter +{ + int Length { get; } + IInner Slice(int a, int b); + object this[int i] { get; } +} +interface IInner +{ + int Count { get; } + IOuter this[Range r] { get; } + object this[Index i] { get; } +} +"; + var comp = CreateCompilationWithIndexAndRangeAndSpan(new[] { source, TestSources.GetSubArray }, options: TestOptions.DebugExe); + comp.VerifyEmitDiagnostics( + // (9,10): error CS8120: The switch case is unreachable. It has already been handled by a previous case or it is impossible to match. + // case [..[10],20]: // 1 + Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, "[..[10],20]").WithLocation(9, 10) + ); + } + + [Fact] + public void Subsumption_Slice_03() + { + var source = @" +#nullable enable +class C +{ + public int Length => 3; + public int this[int i] => 0; + public int[]? Slice(int i, int j) => null; + + public static void Main() + { + switch (new C()) + { + case [.. {}]: + break; + case [.. null]: + break; + } + } +} +"; + var compilation = CreateCompilationWithIndexAndRange(source, options: TestOptions.ReleaseExe); + compilation.VerifyEmitDiagnostics( + // (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 [.. null]: + Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, "[.. null]").WithLocation(15, 18) + ); + } + [Fact] public void Exhaustiveness_01() { @@ -7974,7 +8107,7 @@ public void ListPattern_NullTestOnSlice() switch (a) { case [..[1],2,3]: - case [1,2,3]: // no error + case [1,2,3]: // error break; } "; @@ -7982,7 +8115,10 @@ public void ListPattern_NullTestOnSlice() comp.VerifyEmitDiagnostics( // (7,10): 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]: // error - Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, "[1,2,3]").WithLocation(7, 10) + Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, "[1,2,3]").WithLocation(7, 10), + // (15,10): 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]: // error + Diagnostic(ErrorCode.ERR_SwitchCaseSubsumed, "[1,2,3]").WithLocation(15, 10) ); } } diff --git a/src/Compilers/Test/Core/Assert/AssertEx.cs b/src/Compilers/Test/Core/Assert/AssertEx.cs index a9f22745e31da..7cdcdded82c88 100644 --- a/src/Compilers/Test/Core/Assert/AssertEx.cs +++ b/src/Compilers/Test/Core/Assert/AssertEx.cs @@ -927,10 +927,12 @@ public static void Multiple(bool includeStackTrace, params Action[] assertions) if (exceptions is null) return; - var stringBuilder = new StringBuilder($"{exceptions.Count} out of {assertions.Length} assertions failed."); + var stringBuilder = new StringBuilder() + .AppendLine($"{exceptions.Count} out of {assertions.Length} assertions failed.") + .AppendLine(); foreach (var (index, ex) in exceptions) { - var stack = ex.StackTrace.Split(new[] { "\r\n" }, StringSplitOptions.None); + var stack = ex.StackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None); stringBuilder .AppendLine($"Assertion failed at index {index}:") .AppendLine(stack[^2]) // Prints the failing line in the original test case.