From e1bee98b51fb75c3a6f66da50494cca58ca2ea61 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Fri, 5 Mar 2021 10:12:59 -0800 Subject: [PATCH 1/9] Implement conditional expression changes in definite assignment (#51498) --- .../Portable/FlowAnalysis/AbstractFlowPass.cs | 50 +- .../FlowAnalysis/DefiniteAssignment.cs | 2 + .../Portable/FlowAnalysis/NullableWalker.cs | 21 +- .../Test/Semantic/FlowAnalysis/FlowTests.cs | 431 ++++++++++-------- 4 files changed, 286 insertions(+), 218 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index e7c667ad0eb96..47c6b94572883 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -1115,7 +1115,11 @@ public override BoundNode VisitGlobalStatementInitializer(BoundGlobalStatementIn public override BoundNode VisitLambda(BoundLambda node) => null; - public override BoundNode VisitLocal(BoundLocal node) => null; + public override BoundNode VisitLocal(BoundLocal node) + { + SplitIfBooleanConstant(node); + return null; + } public override BoundNode VisitLocalDeclaration(BoundLocalDeclaration node) { @@ -1459,9 +1463,27 @@ public override BoundNode VisitTypeOrValueExpression(BoundTypeOrValueExpression public override BoundNode VisitLiteral(BoundLiteral node) { + SplitIfBooleanConstant(node); return null; } + protected void SplitIfBooleanConstant(BoundExpression node) + { + if (node.ConstantValue is { IsBoolean: true, BooleanValue: bool booleanValue }) + { + var unreachable = UnreachableState(); + Split(); + if (booleanValue) + { + StateWhenFalse = unreachable; + } + else + { + StateWhenTrue = unreachable; + } + } + } + public override BoundNode VisitMethodDefIndex(BoundMethodDefIndex node) { return null; @@ -1929,6 +1951,7 @@ protected void AfterRightHasBeenVisited(BoundCompoundAssignmentOperator node) public override BoundNode VisitFieldAccess(BoundFieldAccess node) { VisitFieldAccessInternal(node.ReceiverOpt, node.FieldSymbol); + SplitIfBooleanConstant(node); return null; } @@ -2619,23 +2642,34 @@ protected virtual BoundNode VisitConditionalOperatorCore( { VisitConditionalOperand(alternativeState, alternative, isByRef); VisitConditionalOperand(consequenceState, consequence, isByRef); - // it may be a boolean state at this point. } else if (IsConstantFalse(condition)) { VisitConditionalOperand(consequenceState, consequence, isByRef); VisitConditionalOperand(alternativeState, alternative, isByRef); - // it may be a boolean state at this point. } else { + // at this point, the state is conditional after a conditional expression if: + // 1. the state is conditional after the consequence, or + // 2. the state is conditional after the alternative + VisitConditionalOperand(consequenceState, consequence, isByRef); - Unsplit(); - consequenceState = this.State; + var conditionalAfterConsequence = IsConditionalState; + var (afterConsequenceWhenTrue, afterConsequenceWhenFalse) = conditionalAfterConsequence ? (StateWhenTrue, StateWhenFalse) : (State, State); + VisitConditionalOperand(alternativeState, alternative, isByRef); - Unsplit(); - Join(ref this.State, ref consequenceState); - // it may not be a boolean state at this point (5.3.3.28) + if (!conditionalAfterConsequence && !IsConditionalState) + { + // simplify in the common case + Join(ref this.State, ref afterConsequenceWhenTrue); + } + else + { + Split(); + Join(ref this.StateWhenTrue, ref afterConsequenceWhenTrue); + Join(ref this.StateWhenFalse, ref afterConsequenceWhenFalse); + } } return null; diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs index f5ce80db5ba92..594ef05b1e993 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs @@ -1891,6 +1891,8 @@ public override BoundNode VisitLocal(BoundLocal node) { Diagnostics.Add(ErrorCode.ERR_FixedLocalInLambda, new SourceLocation(node.Syntax), localSymbol); } + + SplitIfBooleanConstant(node); return null; } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index b8203415bf38a..388ab801fe924 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -8182,22 +8182,6 @@ private TypeWithAnnotations GetDeclaredParameterResult(ParameterSymbol parameter return null; } - private void SplitIfBooleanConstant(BoundExpression node) - { - if (node.ConstantValue is { IsBoolean: true, BooleanValue: bool booleanValue }) - { - Split(); - if (booleanValue) - { - StateWhenFalse = UnreachableState(); - } - else - { - StateWhenTrue = UnreachableState(); - } - } - } - public override BoundNode? VisitFieldAccess(BoundFieldAccess node) { var updatedSymbol = VisitMemberAccess(node, node.ReceiverOpt, node.FieldSymbol); @@ -9051,12 +9035,9 @@ private TypeWithState InferResultNullabilityOfBinaryLogicalOperator(BoundExpress public override BoundNode? VisitLiteral(BoundLiteral node) { - var result = base.VisitLiteral(node); - Debug.Assert(!IsConditionalState); + var result = base.VisitLiteral(node); SetResultType(node, TypeWithState.Create(node.Type, node.Type?.CanContainNull() != false && node.ConstantValue?.IsNull == true ? NullableFlowState.MaybeDefault : NullableFlowState.NotNull)); - - SplitIfBooleanConstant(node); return result; } diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs index 10056f4eeef70..c7ad52ac310b3 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs @@ -1824,252 +1824,187 @@ public static void M8() " + suffix; CreateCompilation(source).VerifyDiagnostics( - // (61,58): error CS0165: Use of unassigned local variable 'a' + // (87,58): error CS0165: Use of unassigned local variable 'a' // /* NDA --> NDA */ { int a; if (x ? F(a) : F(b)) b = c; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (63,58): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(87, 58), + // (89,58): error CS0165: Use of unassigned local variable 'a' // /* DAF --> NDA */ { int a; if ((x || G(out a)) ? F(a) : F(b)) b = c; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (68,65): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(89, 58), + // (94,65): error CS0165: Use of unassigned local variable 'a' // /* NDA --> NDA */ { int a; if (x ? F(b) : F(a)) b = c; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (69,65): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(94, 65), + // (95,65): error CS0165: Use of unassigned local variable 'a' // /* DAT --> NDA */ { int a; if ((x && G(out a)) ? F(b) : F(a)) b = c; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (75,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(95, 65), + // (101,95): error CS0165: Use of unassigned local variable 'a' // /* NDA?NDA:NDA-->NDA */ { int a; if (x ? y : z) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (76,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(101, 95), + // (102,107): error CS0165: Use of unassigned local variable 'a' // /* NDA?NDA:NDA-->NDA */ { int a; if (x ? y : z) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (77,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(102, 107), + // (103,95): error CS0165: Use of unassigned local variable 'a' // /* NDA?NDA:DAT-->NDA */ { int a; if (x ? y : (z && G(out a))) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (78,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(103, 95), + // (104,107): error CS0165: Use of unassigned local variable 'a' // /* NDA?NDA:DAT-->NDA */ { int a; if (x ? y : (z && G(out a))) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (79,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(104, 107), + // (105,95): error CS0165: Use of unassigned local variable 'a' // /* NDA?NDA:DAF-->NDA */ { int a; if (x ? y : (z || G(out a))) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (80,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(105, 95), + // (106,107): error CS0165: Use of unassigned local variable 'a' // /* NDA?NDA:DAF-->NDA */ { int a; if (x ? y : (z || G(out a))) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (81,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(106, 107), + // (107,95): error CS0165: Use of unassigned local variable 'a' // /* NDA?NDA:DA -->NDA */ { int a; if (x ? y : G(out a)) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (82,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(107, 95), + // (108,107): error CS0165: Use of unassigned local variable 'a' // /* NDA?NDA:DA -->NDA */ { int a; if (x ? y : G(out a)) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (83,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(108, 107), + // (109,95): error CS0165: Use of unassigned local variable 'a' // /* NDA?DAT:NDA-->NDA */ { int a; if (x ? (y && G(out a)) : z) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (84,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(109, 95), + // (110,107): error CS0165: Use of unassigned local variable 'a' // /* NDA?DAT:NDA-->NDA */ { int a; if (x ? (y && G(out a)) : z) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (85,95): error CS0165: Use of unassigned local variable 'a' - // /* NDA?DAT:DAT-->DAT */ { int a; if (x ? (y && G(out a)) : (z && G(out a))) b = a; else d = c; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (86,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(110, 107), + // (112,107): error CS0165: Use of unassigned local variable 'a' // /* NDA?DAT:DAT-->DAT */ { int a; if (x ? (y && G(out a)) : (z && G(out a))) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (87,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(112, 107), + // (113,95): error CS0165: Use of unassigned local variable 'a' // /* NDA?DAT:DAF-->NDA */ { int a; if (x ? (y && G(out a)) : (z || G(out a))) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (88,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(113, 95), + // (114,107): error CS0165: Use of unassigned local variable 'a' // /* NDA?DAT:DAF-->NDA */ { int a; if (x ? (y && G(out a)) : (z || G(out a))) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (89,95): error CS0165: Use of unassigned local variable 'a' - // /* NDA?DAT:DA -->DAT */ { int a; if (x ? (y && G(out a)) : G(out a)) b = a; else d = c; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (90,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(114, 107), + // (116,107): error CS0165: Use of unassigned local variable 'a' // /* NDA?DAT:DA -->DAT */ { int a; if (x ? (y && G(out a)) : G(out a)) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (91,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(116, 107), + // (117,95): error CS0165: Use of unassigned local variable 'a' // /* NDA?DAF:NDA-->NDA */ { int a; if (x ? (y || G(out a)) : z) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (92,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(117, 95), + // (118,107): error CS0165: Use of unassigned local variable 'a' // /* NDA?DAF:NDA-->NDA */ { int a; if (x ? (y || G(out a)) : z) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (93,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(118, 107), + // (119,95): error CS0165: Use of unassigned local variable 'a' // /* NDA?DAF:DAT-->NDA */ { int a; if (x ? (y || G(out a)) : (z && G(out a))) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (94,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(119, 95), + // (120,107): error CS0165: Use of unassigned local variable 'a' // /* NDA?DAF:DAT-->NDA */ { int a; if (x ? (y || G(out a)) : (z && G(out a))) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (95,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(120, 107), + // (121,95): error CS0165: Use of unassigned local variable 'a' // /* NDA?DAF:DAF-->DAF */ { int a; if (x ? (y || G(out a)) : (z || G(out a))) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (96,107): error CS0165: Use of unassigned local variable 'a' - // /* NDA?DAF:DAF-->DAF */ { int a; if (x ? (y || G(out a)) : (z || G(out a))) b = c; else d = a; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (97,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(121, 95), + // (123,95): error CS0165: Use of unassigned local variable 'a' // /* NDA?DAF:DA -->DAF */ { int a; if (x ? (y || G(out a)) : G(out a)) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (98,107): error CS0165: Use of unassigned local variable 'a' - // /* NDA?DAF:DA -->DAF */ { int a; if (x ? (y || G(out a)) : G(out a)) b = c; else d = a; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (99,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(123, 95), + // (125,95): error CS0165: Use of unassigned local variable 'a' // /* NDA?DA :NDA-->NDA */ { int a; if (x ? G(out a) : z) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (100,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(125, 95), + // (126,107): error CS0165: Use of unassigned local variable 'a' // /* NDA?DA :NDA-->NDA */ { int a; if (x ? G(out a) : z) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (101,95): error CS0165: Use of unassigned local variable 'a' - // /* NDA?DA :DAT-->DAT */ { int a; if (x ? G(out a) : (z && G(out a))) b = a; else d = c; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (102,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(126, 107), + // (128,107): error CS0165: Use of unassigned local variable 'a' // /* NDA?DA :DAT-->DAT */ { int a; if (x ? G(out a) : (z && G(out a))) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (103,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(128, 107), + // (129,95): error CS0165: Use of unassigned local variable 'a' // /* NDA?DA :DAF-->DAF */ { int a; if (x ? G(out a) : (z || G(out a))) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (104,107): error CS0165: Use of unassigned local variable 'a' - // /* NDA?DA :DAF-->DAF */ { int a; if (x ? G(out a) : (z || G(out a))) b = c; else d = a; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (106,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(129, 95), + // (132,95): error CS0165: Use of unassigned local variable 'a' // /* DAT?NDA:NDA-->NDA */ { int a; if ((x && G(out a)) ? y : z) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (107,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(132, 95), + // (133,107): error CS0165: Use of unassigned local variable 'a' // /* DAT?NDA:NDA-->NDA */ { int a; if ((x && G(out a)) ? y : z) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (108,95): error CS0165: Use of unassigned local variable 'a' - // /* DAT?NDA:DAT-->DAT */ { int a; if ((x && G(out a)) ? y : (z && G(out a))) b = a; else d = c; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (109,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(133, 107), + // (135,107): error CS0165: Use of unassigned local variable 'a' // /* DAT?NDA:DAT-->DAT */ { int a; if ((x && G(out a)) ? y : (z && G(out a))) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (110,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(135, 107), + // (136,95): error CS0165: Use of unassigned local variable 'a' // /* DAT?NDA:DAF-->DAF */ { int a; if ((x && G(out a)) ? y : (z || G(out a))) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (111,107): error CS0165: Use of unassigned local variable 'a' - // /* DAT?NDA:DAF-->DAF */ { int a; if ((x && G(out a)) ? y : (z || G(out a))) b = c; else d = a; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (113,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(136, 95), + // (139,95): error CS0165: Use of unassigned local variable 'a' // /* DAT?DAT:NDA-->NDA */ { int a; if ((x && G(out a)) ? (y && G(out a)) : z) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (114,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(139, 95), + // (140,107): error CS0165: Use of unassigned local variable 'a' // /* DAT?DAT:NDA-->NDA */ { int a; if ((x && G(out a)) ? (y && G(out a)) : z) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (115,95): error CS0165: Use of unassigned local variable 'a' - // /* DAT?DAT:DAT-->DAT */ { int a; if ((x && G(out a)) ? (y && G(out a)) : (z && G(out a))) b = a; else d = c; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (116,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(140, 107), + // (142,107): error CS0165: Use of unassigned local variable 'a' // /* DAT?DAT:DAT-->DAT */ { int a; if ((x && G(out a)) ? (y && G(out a)) : (z && G(out a))) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (117,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(142, 107), + // (143,95): error CS0165: Use of unassigned local variable 'a' // /* DAT?DAT:DAF-->DAF */ { int a; if ((x && G(out a)) ? (y && G(out a)) : (z || G(out a))) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (118,107): error CS0165: Use of unassigned local variable 'a' - // /* DAT?DAT:DAF-->DAF */ { int a; if ((x && G(out a)) ? (y && G(out a)) : (z || G(out a))) b = c; else d = a; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (120,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(143, 95), + // (146,95): error CS0165: Use of unassigned local variable 'a' // /* DAT?DAF:NDA-->NDA */ { int a; if ((x && G(out a)) ? (y || G(out a)) : z) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (121,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(146, 95), + // (147,107): error CS0165: Use of unassigned local variable 'a' // /* DAT?DAF:NDA-->NDA */ { int a; if ((x && G(out a)) ? (y || G(out a)) : z) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (122,95): error CS0165: Use of unassigned local variable 'a' - // /* DAT?DAF:DAT-->DAT */ { int a; if ((x && G(out a)) ? (y || G(out a)) : (z && G(out a))) b = a; else d = c; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (123,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(147, 107), + // (149,107): error CS0165: Use of unassigned local variable 'a' // /* DAT?DAF:DAT-->DAT */ { int a; if ((x && G(out a)) ? (y || G(out a)) : (z && G(out a))) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (124,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(149, 107), + // (150,95): error CS0165: Use of unassigned local variable 'a' // /* DAT?DAF:DAF-->DAF */ { int a; if ((x && G(out a)) ? (y || G(out a)) : (z || G(out a))) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (125,107): error CS0165: Use of unassigned local variable 'a' - // /* DAT?DAF:DAF-->DAF */ { int a; if ((x && G(out a)) ? (y || G(out a)) : (z || G(out a))) b = c; else d = a; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (127,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(150, 95), + // (153,95): error CS0165: Use of unassigned local variable 'a' // /* DAT?DA :NDA-->NDA */ { int a; if ((x && G(out a)) ? G(out a) : z) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (128,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(153, 95), + // (154,107): error CS0165: Use of unassigned local variable 'a' // /* DAT?DA :NDA-->NDA */ { int a; if ((x && G(out a)) ? G(out a) : z) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (129,95): error CS0165: Use of unassigned local variable 'a' - // /* DAT?DA :DAT-->DAT */ { int a; if ((x && G(out a)) ? G(out a) : (z && G(out a))) b = a; else d = c; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (130,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(154, 107), + // (156,107): error CS0165: Use of unassigned local variable 'a' // /* DAT?DA :DAT-->DAT */ { int a; if ((x && G(out a)) ? G(out a) : (z && G(out a))) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (131,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(156, 107), + // (157,95): error CS0165: Use of unassigned local variable 'a' // /* DAT?DA :DAF-->DAF */ { int a; if ((x && G(out a)) ? G(out a) : (z || G(out a))) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (132,107): error CS0165: Use of unassigned local variable 'a' - // /* DAT?DA :DAF-->DAF */ { int a; if ((x && G(out a)) ? G(out a) : (z || G(out a))) b = c; else d = a; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (134,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(157, 95), + // (160,95): error CS0165: Use of unassigned local variable 'a' // /* DAF?NDA:NDA-->NDA */ { int a; if ((x || G(out a)) ? y : z) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (135,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(160, 95), + // (161,107): error CS0165: Use of unassigned local variable 'a' // /* DAF?NDA:NDA-->NDA */ { int a; if ((x || G(out a)) ? y : z) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (136,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(161, 107), + // (162,95): error CS0165: Use of unassigned local variable 'a' // /* DAF?NDA:DAT-->NDA */ { int a; if ((x || G(out a)) ? y : (z && G(out a))) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (137,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(162, 95), + // (163,107): error CS0165: Use of unassigned local variable 'a' // /* DAF?NDA:DAT-->NDA */ { int a; if ((x || G(out a)) ? y : (z && G(out a))) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (138,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(163, 107), + // (164,95): error CS0165: Use of unassigned local variable 'a' // /* DAF?NDA:DAF-->NDA */ { int a; if ((x || G(out a)) ? y : (z || G(out a))) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (139,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(164, 95), + // (165,107): error CS0165: Use of unassigned local variable 'a' // /* DAF?NDA:DAF-->NDA */ { int a; if ((x || G(out a)) ? y : (z || G(out a))) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (140,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(165, 107), + // (166,107): error CS0165: Use of unassigned local variable 'a' // /* DAF?NDA:DA -->NDA */ { int a; if ((x || G(out a)) ? y : G(out a)) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (141,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(166, 107), + // (167,107): error CS0165: Use of unassigned local variable 'a' // /* DAF?NDA:DA -->NDA */ { int a; if ((x || G(out a)) ? y : G(out a)) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (142,95): error CS0165: Use of unassigned local variable 'a' - // /* DAF?DAT:NDA-->DAT */ { int a; if ((x || G(out a)) ? (y && G(out a)) : z) b = a; else d = c; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (143,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(167, 107), + // (169,107): error CS0165: Use of unassigned local variable 'a' // /* DAF?DAT:NDA-->DAT */ { int a; if ((x || G(out a)) ? (y && G(out a)) : z) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (144,95): error CS0165: Use of unassigned local variable 'a' - // /* DAF?DAT:DAT-->DAT */ { int a; if ((x || G(out a)) ? (y && G(out a)) : (z && G(out a))) b = a; else d = c; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (145,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(169, 107), + // (171,107): error CS0165: Use of unassigned local variable 'a' // /* DAF?DAT:DAT-->DAT */ { int a; if ((x || G(out a)) ? (y && G(out a)) : (z && G(out a))) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (146,95): error CS0165: Use of unassigned local variable 'a' - // /* DAF?DAT:DAF-->DAT */ { int a; if ((x || G(out a)) ? (y && G(out a)) : (z || G(out a))) b = a; else d = c; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (147,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(171, 107), + // (173,107): error CS0165: Use of unassigned local variable 'a' // /* DAF?DAT:DAF-->DAT */ { int a; if ((x || G(out a)) ? (y && G(out a)) : (z || G(out a))) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (148,95): error CS0165: Use of unassigned local variable 'a' - // /* DAF?DAT:DA -->DAT */ { int a; if ((x || G(out a)) ? (y && G(out a)) : G(out a)) b = a; else d = c; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (149,107): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(173, 107), + // (175,107): error CS0165: Use of unassigned local variable 'a' // /* DAF?DAT:DA -->DAT */ { int a; if ((x || G(out a)) ? (y && G(out a)) : G(out a)) b = c; else d = a; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (150,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(175, 107), + // (176,95): error CS0165: Use of unassigned local variable 'a' // /* DAF?DAF:NDA-->DAF */ { int a; if ((x || G(out a)) ? (y || G(out a)) : z) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (151,107): error CS0165: Use of unassigned local variable 'a' - // /* DAF?DAF:NDA-->DAF */ { int a; if ((x || G(out a)) ? (y || G(out a)) : z) b = c; else d = a; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (152,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(176, 95), + // (178,95): error CS0165: Use of unassigned local variable 'a' // /* DAF?DAF:DAT-->DAF */ { int a; if ((x || G(out a)) ? (y || G(out a)) : (z && G(out a))) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (153,107): error CS0165: Use of unassigned local variable 'a' - // /* DAF?DAF:DAT-->DAF */ { int a; if ((x || G(out a)) ? (y || G(out a)) : (z && G(out a))) b = c; else d = a; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (154,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(178, 95), + // (180,95): error CS0165: Use of unassigned local variable 'a' // /* DAF?DAF:DAF-->DAF */ { int a; if ((x || G(out a)) ? (y || G(out a)) : (z || G(out a))) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (155,107): error CS0165: Use of unassigned local variable 'a' - // /* DAF?DAF:DAF-->DAF */ { int a; if ((x || G(out a)) ? (y || G(out a)) : (z || G(out a))) b = c; else d = a; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (156,95): error CS0165: Use of unassigned local variable 'a' + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(180, 95), + // (182,95): error CS0165: Use of unassigned local variable 'a' // /* DAF?DAF:DA -->DAF */ { int a; if ((x || G(out a)) ? (y || G(out a)) : G(out a)) b = a; else d = c; } // Error - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), - // (157,107): error CS0165: Use of unassigned local variable 'a' - // /* DAF?DAF:DA -->DAF */ { int a; if ((x || G(out a)) ? (y || G(out a)) : G(out a)) b = c; else d = a; } // OK - Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a")); + Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(182, 95) + ); } [Fact, WorkItem(529603, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/529603")] @@ -2096,11 +2031,127 @@ static bool Set(out int x) } } "; - // Bug#529603: Won't Fix (Native no error) + CreateCompilation(source).VerifyDiagnostics(); + } + + [Theory] + [InlineData("true", "false")] + [InlineData("FIELD_TRUE", "FIELD_FALSE")] + [InlineData("LOCAL_TRUE", "LOCAL_FALSE")] + [InlineData("true || false", "true && false")] + [InlineData("!false", "!true")] + public void IfConditionalConstant(string @true, string @false) + { + var source = @" +#pragma warning disable 219 // The variable is assigned but its value is never used + +class C +{ + const bool FIELD_TRUE = true; + const bool FIELD_FALSE = false; + + static void M(bool b) + { + const bool LOCAL_TRUE = true; + const bool LOCAL_FALSE = false; + + { + int x, y; + if (b ? Set(out x) : " + @false + @") + y = x; + } + + { + int x, y; + if (b ? Set(out x) : " + @true + @") + y = x; // 1 + } + + { + int x, y; + if (b ? " + @false + @" : Set(out x)) + y = x; + } + + { + int x, y; + if (b ? " + @true + @" : Set(out x)) + y = x; // 2 + } + + static bool Set(out int x) + { + x = 1; + return true; + } + } +} +"; CreateCompilation(source).VerifyDiagnostics( - // (11,21): error CS0165: Use of unassigned local variable 'x' - // int y = x; // x is definitely assigned if we reach this point - Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x")); + // (23,21): error CS0165: Use of unassigned local variable 'x' + // y = x; // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(23, 21), + // (35,21): error CS0165: Use of unassigned local variable 'x' + // y = x; // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(35, 21) + ); + } + + [Fact] + public void IfConditional_ComplexCondition_ConstantConsequence() + { + var source = @" +class C +{ + bool M0(int x) => true; + + void M1(bool b) + { + int x; + _ = (b && M0(x = 0) ? true : false) + ? x.ToString() + : x.ToString(); // 1 + } + + void M2(bool b) + { + int x; + _ = (b && M0(x = 0) ? false : true) + ? x.ToString() // 2 + : x.ToString(); + } + + void M3(bool b) + { + int x; + _ = (b || M0(x = 0) ? true : false) + ? x.ToString() // 3 + : x.ToString(); + } + + void M4(bool b) + { + int x; + _ = (b || M0(x = 0) ? false : true) + ? x.ToString() + : x.ToString(); // 4 + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (11,15): error CS0165: Use of unassigned local variable 'x' + // : x.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(11, 15), + // (18,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(18, 15), + // (26,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(26, 15), + // (35,15): error CS0165: Use of unassigned local variable 'x' + // : x.ToString(); // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(35, 15)); } [WorkItem(545352, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545352")] From 91443c562c3a43ace821b588b1b97bb72a7c624f Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 22 Mar 2021 16:38:26 -0700 Subject: [PATCH 2/9] Null coalescing in definite assignment (#51567) --- .../Portable/FlowAnalysis/AbstractFlowPass.cs | 213 +++-- .../Test/Semantic/FlowAnalysis/FlowTests.cs | 729 +++++++++++++++++- .../FlowAnalysis/RegionAnalysisTests.cs | 29 + 3 files changed, 897 insertions(+), 74 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index 47c6b94572883..ca021c54186d9 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Text; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -249,6 +250,14 @@ protected string DumpLabels() } #endif + private void EnterRegionIfNeeded(BoundNode node) + { + if (TrackingRegions && node == this.firstInRegion && this.regionPlace == RegionPlace.Before) + { + EnterRegion(); + } + } + /// /// Subclasses may override EnterRegion to perform any actions at the entry to the region. /// @@ -258,6 +267,14 @@ protected virtual void EnterRegion() this.regionPlace = RegionPlace.Inside; } + private void LeaveRegionIfNeeded(BoundNode node) + { + if (TrackingRegions && node == this.lastInRegion && this.regionPlace == RegionPlace.Inside) + { + LeaveRegion(); + } + } + /// /// Subclasses may override LeaveRegion to perform any action at the end of the region. /// @@ -327,23 +344,9 @@ protected BoundNode VisitAlways(BoundNode node) // We scan even expressions, because we must process lambdas contained within them. if (node != null) { - if (TrackingRegions) - { - if (node == this.firstInRegion && this.regionPlace == RegionPlace.Before) - { - EnterRegion(); - } - - result = VisitWithStackGuard(node); - if (node == this.lastInRegion && this.regionPlace == RegionPlace.Inside) - { - LeaveRegion(); - } - } - else - { - result = VisitWithStackGuard(node); - } + EnterRegionIfNeeded(node); + VisitWithStackGuard(node); + LeaveRegionIfNeeded(node); } return result; @@ -541,10 +544,7 @@ protected void SetUnreachable() protected void VisitLvalue(BoundExpression node) { - if (TrackingRegions && node == this.firstInRegion && this.regionPlace == RegionPlace.Before) - { - EnterRegion(); - } + EnterRegionIfNeeded(node); switch (node?.Kind) { @@ -599,10 +599,7 @@ protected void VisitLvalue(BoundExpression node) break; } - if (TrackingRegions && node == this.lastInRegion && this.regionPlace == RegionPlace.Inside) - { - LeaveRegion(); - } + LeaveRegionIfNeeded(node); } protected virtual void VisitLvalue(BoundLocal node) @@ -1417,23 +1414,9 @@ public override BoundNode VisitDelegateCreationExpression(BoundDelegateCreationE { if ((object)node.MethodOpt != null && node.MethodOpt.RequiresInstanceReceiver) { - if (TrackingRegions) - { - if (methodGroup == this.firstInRegion && this.regionPlace == RegionPlace.Before) - { - EnterRegion(); - } - - VisitRvalue(methodGroup.ReceiverOpt); - if (methodGroup == this.lastInRegion && IsInside) - { - LeaveRegion(); - } - } - else - { - VisitRvalue(methodGroup.ReceiverOpt); - } + EnterRegionIfNeeded(methodGroup); + VisitRvalue(methodGroup.ReceiverOpt); + LeaveRegionIfNeeded(methodGroup); } else if (node.MethodOpt?.OriginalDefinition is LocalFunctionSymbol localFunc) { @@ -1522,23 +1505,9 @@ public override BoundNode VisitConversion(BoundConversion node) { BoundExpression receiver = ((BoundMethodGroup)node.Operand).ReceiverOpt; // A method group's "implicit this" is only used for instance methods. - if (TrackingRegions) - { - if (node.Operand == this.firstInRegion && this.regionPlace == RegionPlace.Before) - { - EnterRegion(); - } - - VisitRvalue(receiver); - if (node.Operand == this.lastInRegion && IsInside) - { - LeaveRegion(); - } - } - else - { - VisitRvalue(receiver); - } + EnterRegionIfNeeded(node.Operand); + VisitRvalue(receiver); + LeaveRegionIfNeeded(node.Operand); } else if (node.SymbolOpt?.OriginalDefinition is LocalFunctionSymbol localFunc) { @@ -2455,33 +2424,105 @@ public override BoundNode VisitMethodGroup(BoundMethodGroup node) return null; } - public override BoundNode VisitNullCoalescingOperator(BoundNullCoalescingOperator node) +#nullable enable + + public override BoundNode? VisitNullCoalescingOperator(BoundNullCoalescingOperator node) { - VisitRvalue(node.LeftOperand); if (IsConstantNull(node.LeftOperand)) { - VisitRvalue(node.RightOperand); + VisitRvalue(node.LeftOperand); + Visit(node.RightOperand); } else { - var savedState = this.State.Clone(); + TLocalState savedState = VisitPossibleConditionalAccess(node.LeftOperand, out var stateWhenNotNull) + ? stateWhenNotNull + : State.Clone(); + if (node.LeftOperand.ConstantValue != null) { SetUnreachable(); } - VisitRvalue(node.RightOperand); - Join(ref this.State, ref savedState); + Visit(node.RightOperand); + if (IsConditionalState) + { + Join(ref StateWhenTrue, ref savedState); + Join(ref StateWhenFalse, ref savedState); + } + else + { + Join(ref this.State, ref savedState); + } } return null; } - public override BoundNode VisitConditionalAccess(BoundConditionalAccess node) + /// + /// Unconditionally visits an expression. + /// If the expression has "state when not null" after visiting, + /// the method returns 'true' and writes the state to . + /// + protected bool VisitPossibleConditionalAccess(BoundExpression node, [NotNullWhen(true)] out TLocalState? stateWhenNotNull) + { + EnterRegionIfNeeded(node); + var hasStateWhenNotNull = visit(out stateWhenNotNull); + Debug.Assert(!IsConditionalState); + LeaveRegionIfNeeded(node); + return hasStateWhenNotNull; + + bool visit([NotNullWhen(true)] out TLocalState? stateWhenNotNull) + { + var access = node switch + { + BoundConditionalAccess ca => ca, + BoundConversion { Conversion: Conversion innerConversion, Operand: BoundConditionalAccess ca } when isAcceptableConversion(ca, innerConversion) => ca, + _ => null + }; + + if (access is not null) + { + return VisitConditionalAccess(access, out stateWhenNotNull); + } + else + { + stateWhenNotNull = default; + VisitWithStackGuard(node); + Unsplit(); + return false; + } + } + + // "State when not null" can only propagate out of a conditional access if + // it is not subject to a user-defined conversion whose parameter is not of a non-nullable value type. + static bool isAcceptableConversion(BoundConditionalAccess operand, Conversion conversion) + { + if (conversion.Kind is not (ConversionKind.ImplicitUserDefined or ConversionKind.ExplicitUserDefined)) + { + return true; + } + + var method = conversion.Method; + Debug.Assert(method is not null); + Debug.Assert(method.ParameterCount is 1); + var param = method.Parameters[0]; + if (operand.Type.IsNullableType() && param.Type.IsNonNullableValueType()) + { + return true; + } + + return false; + } + } + + private bool VisitConditionalAccess(BoundConditionalAccess node, [NotNullWhen(true)] out TLocalState? stateWhenNotNull) { VisitRvalue(node.Receiver); if (node.Receiver.ConstantValue != null && !IsConstantNull(node.Receiver)) { VisitRvalue(node.AccessExpression); + stateWhenNotNull = default; + return false; } else { @@ -2491,12 +2532,42 @@ public override BoundNode VisitConditionalAccess(BoundConditionalAccess node) SetUnreachable(); } - VisitRvalue(node.AccessExpression); - Join(ref this.State, ref savedState); + // We want to preserve stateWhenNotNull from accesses in the same "chain": + // a?.b(out x)?.c(out y); // expected to preserve stateWhenNotNull from both ?.b(out x) and ?.c(out y) + // but not accesses in nested expressions: + // a?.b(out x, c?.d(out y)); // expected to preserve stateWhenNotNull from a?.b(out x, ...) but not from c?.d(out y) + BoundExpression expr = node.AccessExpression; + while (expr is BoundConditionalAccess innerCondAccess) + { + // we assume that non-conditional accesses can never contain conditional accesses from the same "chain". + // that is, we never have to dig through non-conditional accesses to find and handle conditional accesses. + VisitRvalue(innerCondAccess.Receiver); + expr = innerCondAccess.AccessExpression; + + // PROTOTYPE(improved-definite-assignment): Add NullableWalker implementation and tests which exercises this. + // The savedState here represents the scenario where 0 or more of the access expressions could have been evaluated. + // e.g. after visiting `a?.b(x = null)?.c(x = new object())`, the "state when not null" of `x` is NotNull, but the "state when maybe null" of `x` is MaybeNull. + Join(ref savedState, ref State); + } + + Debug.Assert(expr is BoundExpression); + VisitRvalue(expr); + + stateWhenNotNull = State; + State = savedState; + Join(ref State, ref stateWhenNotNull); + return true; } + } + + public override BoundNode? VisitConditionalAccess(BoundConditionalAccess node) + { + VisitConditionalAccess(node, stateWhenNotNull: out _); return null; } +#nullable disable + public override BoundNode VisitLoweredConditionalAccess(BoundLoweredConditionalAccess node) { VisitRvalue(node.Receiver); @@ -2628,7 +2699,9 @@ public override BoundNode VisitConditionalOperator(BoundConditionalOperator node return VisitConditionalOperatorCore(node, node.IsRef, node.Condition, node.Consequence, node.Alternative); } - protected virtual BoundNode VisitConditionalOperatorCore( +#nullable enable + + protected virtual BoundNode? VisitConditionalOperatorCore( BoundExpression node, bool isByRef, BoundExpression condition, @@ -2675,6 +2748,8 @@ protected virtual BoundNode VisitConditionalOperatorCore( return null; } +#nullable disable + private void VisitConditionalOperand(TLocalState state, BoundExpression operand, bool isByRef) { SetState(state); diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs index c7ad52ac310b3..67cfe75cabcd7 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs @@ -2034,6 +2034,727 @@ static bool Set(out int x) CreateCompilation(source).VerifyDiagnostics(); } + [Fact] + public void CondAccess_NullCoalescing_01() + { + var source = @" +class C +{ + void M1(C c) + { + int x, y; + _ = c?.M(out x, out y) ?? true + ? x.ToString() // 1 + : y.ToString(); + } + + void M2(C c) + { + int x, y; + _ = c?.M(out x, out y) ?? false + ? x.ToString() + : y.ToString(); // 2; + } + + bool M(out int x, out int y) { x = 42; y = 42; return true; } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (8,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(8, 15), + // (17,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2; + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(17, 15)); + } + + [Fact] + public void CondAccess_NullCoalescing_02() + { + var source = @" +class C +{ + void M1(C c1, C c2) + { + int x, y; + _ = c1?.M(out x, out y) ?? c2?.M(out x, out y) ?? true + ? x.ToString() // 1 + : y.ToString(); + } + + void M2(C c1, C c2) + { + int x, y; + _ = c1?.M(out x, out y) ?? c2?.M(out x, out y) ?? false + ? x.ToString() + : y.ToString(); // 2; + } + + bool M(out int x, out int y) { x = 42; y = 42; return true; } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (8,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(8, 15), + // (17,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2; + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(17, 15)); + } + + [Fact] + public void CondAccess_NullCoalescing_03() + { + var source = @" +class C +{ + void M1(C c1) + { + int x, y; + _ = c1?.MA().MB(out x, out y) ?? false + ? x.ToString() + : y.ToString(); // 1 + } + + void M2(C c1) + { + int x, y; + _ = c1?.MA()?.MB(out x, out y) ?? false + ? x.ToString() + : y.ToString(); // 2 + } + + C MA() { return this; } + bool MB(out int x, out int y) { x = 42; y = 42; return true; } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (9,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(9, 15), + // (17,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(17, 15)); + } + + [Fact] + public void CondAccess_NullCoalescing_04() + { + var source = @" +class C +{ + void M1(C c1) + { + int x, y; + _ = c1?.MA(out x, out y).MB() ?? false + ? x.ToString() + : y.ToString(); // 1 + } + + void M2(C c1) + { + int x, y; + _ = c1?.MA(out x, out y)?.MB() ?? false + ? x.ToString() + : y.ToString(); // 2 + } + + C MA(out int x, out int y) { x = 42; y = 42; return this; } + bool MB() { return true; } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (9,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(9, 15), + // (17,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(17, 15)); + } + + [Fact] + public void CondAccess_NullCoalescing_05() + { + var source = @" +class C +{ + void M1(C c1) + { + int x, y; + _ = c1?.MA(c1.MB(out x, out y)) ?? false + ? x.ToString() + : y.ToString(); // 1 + } + + void M2(C c1) + { + int x, y; + _ = c1?.MA(c1?.MB(out x, out y)) ?? false + ? x.ToString() // 2 + : y.ToString(); // 3 + } + + bool MA(object obj) { return true; } + C MB(out int x, out int y) { x = 42; y = 42; return this; } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (9,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(9, 15), + // (16,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(16, 15), + // (17,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(17, 15)); + } + + [Fact] + public void CondAccess_NullCoalescing_06() + { + var source = @" +class C +{ + void M1(C c1) + { + int x, y; + _ = c1?.M(x = y = 0) ?? true + ? x.ToString() // 1 + : y.ToString(); + } + + void M2(C c1) + { + int x, y; + _ = c1?.M(x = y = 0) ?? false + ? x.ToString() + : y.ToString(); // 2 + } + + bool M(object obj) { return true; } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (8,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(8, 15), + // (17,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(17, 15)); + } + + [Fact] + public void CondAccess_NullCoalescing_07() + { + var source = @" +class C +{ + void M1(bool b, C c1) + { + int x, y; + _ = c1?.M(x = y = 0) ?? b + ? x.ToString() // 1 + : y.ToString(); // 2 + } + + bool M(object obj) { return true; } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (8,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(8, 15), + // (9,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(9, 15)); + } + + [Fact] + public void CondAccess_NullCoalescing_08() + { + var source = @" +class C +{ + void M1(C c1) + { + int x, y; + _ = (c1?.M(x = y = 0)) ?? false + ? x.ToString() + : y.ToString(); // 1 + } + + void M2(C c1) + { + int x, y; + _ = c1?.M(x = y = 0)! ?? false + ? x.ToString() + : y.ToString(); // 2 + } + + void M3(C c1) + { + int x, y; + _ = (c1?.M(x = y = 0))! ?? false + ? x.ToString() + : y.ToString(); // 3 + } + + bool M(object obj) { return true; } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (9,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(9, 15), + // (17,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(17, 15), + // (25,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(25, 15)); + } + + [Fact] + public void CondAccess_NullCoalescing_09() + { + var source = @" +class C +{ + + void M1(C c1) + { + int x, y; + _ = (c1?[x = y = 0]) ?? false + ? x.ToString() + : y.ToString(); // 1 + } + + void M2(C c1) + { + int x, y; + _ = (c1?[x = y = 0]) ?? true + ? x.ToString() // 2 + : y.ToString(); + } + + public bool this[int x] => false; +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (10,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(10, 15), + // (17,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(17, 15)); + } + + [Fact] + public void CondAccess_NullCoalescing_10() + { + var source = @" +class C +{ + + void M1(C c1) + { + int x, y; + _ = (bool?)null ?? (c1?.M(x = y = 0) ?? false) + ? x.ToString() + : y.ToString(); // 1 + } + + void M2(C c1) + { + int x, y; + _ = (bool?)null ?? (c1?.M(x = y = 0) ?? true) + ? x.ToString() // 2 + : y.ToString(); + } + + bool M(object obj) { return true; } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (10,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(10, 15), + // (17,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(17, 15)); + } + + [Fact] + public void NullCoalescing_ConditionalLeft() + { + var source = @" +class C +{ + void M1(C c1, bool b) + { + int x, y; + _ = (bool?)(b && c1.M(x = y = 0)) ?? false + ? x.ToString() // 1 + : y.ToString(); // 2 + } + + void M2(C c1, bool b) + { + int x, y; + _ = (bool?)c1.M(x = y = 0) ?? false + ? x.ToString() + : y.ToString(); + } + + void M3(C c1, bool b) + { + int x, y; + _ = (bool?)((y = 0) is 0 && c1.M(x = 0)) ?? false + ? x.ToString() // 3 + : y.ToString(); + } + + bool M(object obj) { return true; } +} +"; + // Note that in definite assignment we unsplit any conditional state after visiting the left side of `??`. + CreateCompilation(source).VerifyDiagnostics( + // (8,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(8, 15), + // (9,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(9, 15), + // (24,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(24, 15)); + } + + [Fact] + public void NullCoalescing_CondAccess_Throw() + { + var source = @" +class C +{ + void M1(C c1, bool b) + { + int x; + _ = c1?.M(x = 0) ?? throw new System.Exception(); + x.ToString(); + } + + bool M(object obj) { return true; } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void NullCoalescing_CondAccess_Cast() + { + var source = @" +class C +{ + void M1(C c1, bool b) + { + int x; + _ = (object)c1?.M(x = 0) ?? throw new System.Exception(); + x.ToString(); + } + + C M(object obj) { return this; } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void NullCoalescing_CondAccess_UserDefinedConv_01() + { + var source = @" +struct S { } + +struct C +{ + public static implicit operator S(C? c) + { + return default(S); + } + + void M1(C? c1) + { + int x; + S s = c1?.M2(x = 0) ?? c1.Value.M3(x = 0); + x.ToString(); + } + + C M2(object obj) { return this; } + S M3(object obj) { return this; } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void NullCoalescing_CondAccess_UserDefinedConv_02() + { + var source = @" +class B { } +class C +{ + public static implicit operator B(C c) => new B(); + + void M1(C c1) + { + int x; + B b = c1?.M1(x = 0) ?? c1!.M2(x = 0); + x.ToString(); + } + + C M1(object obj) { return this; } + B M2(object obj) { return new B(); } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void NullCoalescing_CondAccess_UserDefinedConv_03() + { + var source = @" +struct B { } +struct C +{ + public static implicit operator B(C c) => new B(); + + void M1(C? c1) + { + int x; + B b = c1?.M1(x = 0) ?? c1!.Value.M2(x = 0); + x.ToString(); + } + + C M1(object obj) { return this; } + B M2(object obj) { return new B(); } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void NullCoalescing_CondAccess_UserDefinedConv_04() + { + var source = @" +struct B { } +struct C +{ + public static implicit operator B?(C c) => null; + + void M1(C? c1) + { + int x; + B? b = c1?.M1(x = 0) ?? c1!.Value.M2(x = 0); + x.ToString(); + } + + C M1(object obj) { return this; } + B? M2(object obj) { return new B(); } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Fact] + public void NullCoalescing_CondAccess_UserDefinedConv_05() + { + var source = @" +struct B +{ + public static implicit operator B(C c) => default; +} + +class C +{ + static void M1(C c1) + { + int x; + B b = c1?.M1(x = 0) ?? c1.M2(x = 0); + x.ToString(); + } + + C M1(object obj) { return this; } + B M2(object obj) { return this; } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_01(string conversionKind) + { + var source = @" +struct S { } + +struct C +{ + public static " + conversionKind + @" operator S(C? c) + { + return default(S); + } + + void M1(C? c1) + { + int x; + S s = (S?)c1?.M2(x = 0) ?? c1.Value.M3(x = 0); + x.ToString(); // 1 + } + + C M2(object obj) { return this; } + S M3(object obj) { return (S)this; } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (15,9): error CS0165: Use of unassigned local variable 'x' + // x.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(15, 9)); + } + + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_02(string conversionKind) + { + var source = @" +class B { } +class C +{ + public static " + conversionKind + @" operator B(C c) => new B(); + + void M1(C c1) + { + int x; + B b = (B)c1?.M1(x = 0) ?? c1!.M2(x = 0); + x.ToString(); // 1 + } + + C M1(object obj) { return this; } + B M2(object obj) { return new B(); } +} +"; + // If the LHS of a `??` is cast using a user-defined conversion whose parameter + // is not a non-nullable value type, we can't propagate out the "state when not null" + // because we can't know whether the conditional access itself was non-null. + CreateCompilation(source).VerifyDiagnostics( + // (11,9): error CS0165: Use of unassigned local variable 'x' + // x.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(11, 9)); + } + + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_03(string conversionKind) + { + var source = @" +struct B { } +struct C +{ + public static " + conversionKind + @" operator B(C c) => new B(); + + void M1(C? c1) + { + int x; + B b = (B?)c1?.M1(x = 0) ?? c1!.Value.M2(x = 0); + x.ToString(); + } + + C M1(object obj) { return this; } + B M2(object obj) { return new B(); } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_04(string conversionKind) + { + var source = @" +struct B { } +struct C +{ + public static " + conversionKind + @" operator B?(C c) => null; + + void M1(C? c1) + { + int x; + B? b = (B?)c1?.M1(x = 0) ?? c1!.Value.M2(x = 0); + x.ToString(); + } + + C M1(object obj) { return this; } + B? M2(object obj) { return new B(); } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_05(string conversionKind) + { + var source = @" +struct B +{ + public static " + conversionKind + @" operator B(C c) => default; +} + +class C +{ + static void M1(C c1) + { + int x; + B b = (B?)c1?.M1(x = 0) ?? c1.M2(x = 0); + x.ToString(); // 1 + } + + C M1(object obj) { return this; } + B M2(object obj) { return default; } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,9): error CS0165: Use of unassigned local variable 'x' + // x.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(13, 9)); + } + + [Fact] + public void NullCoalescing_CondAccess_NullableEnum() + { + var source = @" +public enum E { E1 = 1 } + +public static class Extensions +{ + public static E M1(this E e, object obj) => e; + + static void M2(E? e) + { + int x; + E e2 = e?.M1(x = 0) ?? e.Value.M1(x = 0); + x.ToString(); + } +} +"; + CreateCompilation(source).VerifyDiagnostics(); + } + + [WorkItem(529603, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/529603")] [Theory] [InlineData("true", "false")] [InlineData("FIELD_TRUE", "FIELD_FALSE")] @@ -2410,17 +3131,15 @@ static void Main() C o; var d = new C(); + // equivalent to: + // var v = d != null ? d.M1(out o) : (o = null); var v = d ?. M1(out o) ?? (o = null); System.Console.WriteLine(o); } } "; - CreateCompilationWithMscorlib45(source).VerifyDiagnostics( - // (17,34): error CS0165: Use of unassigned local variable 'o' - // System.Console.WriteLine(o); - Diagnostic(ErrorCode.ERR_UseDefViolation, "o").WithArguments("o").WithLocation(17, 34) - ); + CreateCompilationWithMscorlib45(source).VerifyDiagnostics(); } [Fact] diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs index 702f3242a7332..2125f2a8967c5 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs @@ -2517,6 +2517,35 @@ void assertAllInfo(DataFlowAnalysis dataFlowAnalysis, string currentX, string cu } } + [Fact] + public void CondAccess_NullCoalescing_DataFlow() + { + // This test corresponds to ExtractMethodTests.TestFlowStateNullableParameters3 + var dataFlowAnalysis = CompileAndAnalyzeDataFlowExpression(@" +#nullable enable +class C +{ + public string M() + { + string? a = null; + string? b = null; + return /**/(a + b + a)?.ToString()/**/ ?? string.Empty; + } +} +"); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.VariablesDeclared)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.AlwaysAssigned)); + Assert.Equal("a, b", GetSymbolNamesJoined(dataFlowAnalysis.DataFlowsIn)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.DataFlowsOut)); + Assert.Equal("a, b", GetSymbolNamesJoined(dataFlowAnalysis.ReadInside)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.ReadOutside)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.WrittenInside)); + Assert.Equal("this, a, b", GetSymbolNamesJoined(dataFlowAnalysis.WrittenOutside)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.Captured)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.CapturedInside)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.CapturedOutside)); + } + #endregion #region "Statements" From 0eea7b1b883f8a1ca45f02a18e72a020b8b0a291 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 12 Apr 2021 16:48:38 -0700 Subject: [PATCH 3/9] Learn from bool constants and conditional accesses inside ==/!= (#52425) --- .../Portable/FlowAnalysis/AbstractFlowPass.cs | 183 ++- .../Test/Semantic/FlowAnalysis/FlowTests.cs | 1157 ++++++++++++++++- 2 files changed, 1287 insertions(+), 53 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index c5707faad9afd..2acef1c397f5c 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -121,6 +121,11 @@ internal abstract partial class AbstractFlowPass private readonly bool _nonMonotonicTransfer; + protected void SetConditionalState((TLocalState whenTrue, TLocalState whenFalse) state) + { + SetConditionalState(state.whenTrue, state.whenFalse); + } + protected void SetConditionalState(TLocalState whenTrue, TLocalState whenFalse) { IsConditionalState = true; @@ -2232,24 +2237,110 @@ private void VisitBinaryOperatorChildren(BoundBinaryOperator node) stack.Free(); } +#nullable enable protected virtual void VisitBinaryOperatorChildren(ArrayBuilder stack) { var binary = stack.Pop(); - VisitRvalue(binary.Left); - while (true) + // Only the leftmost operator of a left-associative binary operator chain can learn from a conditional access on the left + // For simplicity, we just special case it here. + // For example, `a?.b(out x) == true` has a conditional access on the left of the operator, + // but `expr == a?.b(out x) == true` has a conditional access on the right of the operator + if (VisitPossibleConditionalAccess(binary.Left, out var stateWhenNotNull) + && canLearnFromOperator(binary) + && isKnownNullOrNotNull(binary.Right)) { VisitRvalue(binary.Right); + Meet(ref stateWhenNotNull, ref State); + var isNullConstant = binary.Right.ConstantValue?.IsNull == true; + SetConditionalState(isNullConstant == isEquals(binary) + ? (State, stateWhenNotNull) + : (stateWhenNotNull, State)); + + if (stack.Count == 0) + { + return; + } + + binary = stack.Pop(); + } + + while (true) + { + if (!canLearnFromOperator(binary) + || !learnFromOperator(binary)) + { + Unsplit(); + Visit(binary.Right); + } if (stack.Count == 0) { break; } - Unsplit(); // VisitRvalue does this binary = stack.Pop(); } + + static bool canLearnFromOperator(BoundBinaryOperator binary) + { + var kind = binary.OperatorKind; + return kind.Operator() is BinaryOperatorKind.Equal or BinaryOperatorKind.NotEqual + && (!kind.IsUserDefined() || kind.IsLifted()); + } + + static bool isKnownNullOrNotNull(BoundExpression expr) + { + return expr.ConstantValue is object + || (expr is BoundConversion { ConversionKind: ConversionKind.ExplicitNullable or ConversionKind.ImplicitNullable } conv + && conv.Operand.Type!.IsNonNullableValueType()); + } + + static bool isEquals(BoundBinaryOperator binary) + => binary.OperatorKind.Operator() == BinaryOperatorKind.Equal; + + // Returns true if `binary.Right` was visited by the call. + bool learnFromOperator(BoundBinaryOperator binary) + { + // `true == a?.b(out x)` + if (isKnownNullOrNotNull(binary.Left) && TryVisitConditionalAccess(binary.Right, out var stateWhenNotNull)) + { + var isNullConstant = binary.Left.ConstantValue?.IsNull == true; + SetConditionalState(isNullConstant == isEquals(binary) + ? (State, stateWhenNotNull) + : (stateWhenNotNull, State)); + + return true; + } + // `a && b(out x) == true` + else if (IsConditionalState && binary.Right.ConstantValue is { IsBoolean: true } rightConstant) + { + var (stateWhenTrue, stateWhenFalse) = (StateWhenTrue.Clone(), StateWhenFalse.Clone()); + Unsplit(); + Visit(binary.Right); + SetConditionalState(isEquals(binary) == rightConstant.BooleanValue + ? (stateWhenTrue, stateWhenFalse) + : (stateWhenFalse, stateWhenTrue)); + + return true; + } + // `true == a && b(out x)` + else if (binary.Left.ConstantValue is { IsBoolean: true } leftConstant) + { + Unsplit(); + Visit(binary.Right); + if (IsConditionalState && isEquals(binary) != leftConstant.BooleanValue) + { + SetConditionalState(StateWhenFalse, StateWhenTrue); + } + + return true; + } + + return false; + } } +#nullable disable public override BoundNode VisitUnaryOperator(BoundUnaryOperator node) { @@ -2445,9 +2536,17 @@ public override BoundNode VisitMethodGroup(BoundMethodGroup node) } else { - TLocalState savedState = VisitPossibleConditionalAccess(node.LeftOperand, out var stateWhenNotNull) - ? stateWhenNotNull - : State.Clone(); + TLocalState savedState; + if (VisitPossibleConditionalAccess(node.LeftOperand, out var stateWhenNotNull)) + { + Debug.Assert(!IsConditionalState); + savedState = stateWhenNotNull; + } + else + { + Unsplit(); + savedState = State.Clone(); + } if (node.LeftOperand.ConstantValue != null) { @@ -2468,40 +2567,31 @@ public override BoundNode VisitMethodGroup(BoundMethodGroup node) } /// - /// Unconditionally visits an expression. - /// If the expression has "state when not null" after visiting, - /// the method returns 'true' and writes the state to . + /// Visits a node only if it is a conditional access. + /// Returns 'true' if and only if the node was visited. /// - protected bool VisitPossibleConditionalAccess(BoundExpression node, [NotNullWhen(true)] out TLocalState? stateWhenNotNull) + private bool TryVisitConditionalAccess(BoundExpression node, [NotNullWhen(true)] out TLocalState? stateWhenNotNull) { - EnterRegionIfNeeded(node); - var hasStateWhenNotNull = visit(out stateWhenNotNull); - Debug.Assert(!IsConditionalState); - LeaveRegionIfNeeded(node); - return hasStateWhenNotNull; - - bool visit([NotNullWhen(true)] out TLocalState? stateWhenNotNull) + var access = node switch { - var access = node switch - { - BoundConditionalAccess ca => ca, - BoundConversion { Conversion: Conversion innerConversion, Operand: BoundConditionalAccess ca } when isAcceptableConversion(ca, innerConversion) => ca, - _ => null - }; + BoundConditionalAccess ca => ca, + BoundConversion { Conversion: Conversion innerConversion, Operand: BoundConditionalAccess ca } when isAcceptableConversion(ca, innerConversion) => ca, + _ => null + }; - if (access is not null) - { - return VisitConditionalAccess(access, out stateWhenNotNull); - } - else - { - stateWhenNotNull = default; - VisitWithStackGuard(node); - Unsplit(); - return false; - } + if (access is not null) + { + EnterRegionIfNeeded(access); + Unsplit(); + VisitConditionalAccess(access, out stateWhenNotNull); + Debug.Assert(!IsConditionalState); + LeaveRegionIfNeeded(access); + return true; } + stateWhenNotNull = default; + return false; + // "State when not null" can only propagate out of a conditional access if // it is not subject to a user-defined conversion whose parameter is not of a non-nullable value type. static bool isAcceptableConversion(BoundConditionalAccess operand, Conversion conversion) @@ -2524,15 +2614,32 @@ static bool isAcceptableConversion(BoundConditionalAccess operand, Conversion co } } - private bool VisitConditionalAccess(BoundConditionalAccess node, [NotNullWhen(true)] out TLocalState? stateWhenNotNull) + /// + /// Unconditionally visits an expression. + /// If the expression has "state when not null" after visiting, + /// the method returns 'true' and writes the state to . + /// + private bool VisitPossibleConditionalAccess(BoundExpression node, [NotNullWhen(true)] out TLocalState? stateWhenNotNull) + { + if (TryVisitConditionalAccess(node, out stateWhenNotNull)) + { + return true; + } + else + { + Visit(node); + return false; + } + } + + private void VisitConditionalAccess(BoundConditionalAccess node, out TLocalState stateWhenNotNull) { VisitRvalue(node.Receiver); if (node.Receiver.ConstantValue != null && !IsConstantNull(node.Receiver)) { VisitRvalue(node.AccessExpression); - stateWhenNotNull = default; - return false; + stateWhenNotNull = this.State.Clone(); } else { @@ -2566,7 +2673,6 @@ private bool VisitConditionalAccess(BoundConditionalAccess node, [NotNullWhen(tr stateWhenNotNull = State; State = savedState; Join(ref State, ref stateWhenNotNull); - return true; } } @@ -3310,4 +3416,3 @@ private void VisitMethodBodies(BoundBlock blockBody, BoundBlock expressionBody) /// internal enum RegionPlace { Before, Inside, After }; } - diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs index 67cfe75cabcd7..0fca9adc9fb06 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs @@ -293,7 +293,7 @@ public void T100() { { int a; if (fFalse && G(out a)) F(a); } // Error { int a; if (fFalse && G(out a)) F(a); else No(); } // Error { int a; if (fFalse && G(out a)) No(); else F(a); } // Error - { int a; if (fFalse && G(out a)) No(); F(a); } // Error + { int a; if (fFalse && G(out a)) No(); F(a); } // Error { int a; if (fFalse && G(out a)) No(); else No(); F(a); } // Error // Unassigned. Unreachable expr considered assigned. @@ -327,10 +327,10 @@ public void T100() { // Unassigned. G(out a) is unreachable expr. { int a; if (fTrue || G(out a)) F(a); } // Error - { int a; if (fTrue || G(out a)) F(a); else No(); } // Error + { int a; if (fTrue || G(out a)) F(a); else No(); } // Error { int a; if (fTrue || G(out a)) No(); else F(a); } // Error { int a; if (fTrue || G(out a)) No(); F(a); } // Error - { int a; if (fTrue || G(out a)) No(); else No(); F(a); } // Error + { int a; if (fTrue || G(out a)) No(); else No(); F(a); } // Error { int a; if (fTrue || G(out a)) G(out a); else No(); F(a); } // Error // Assigned. @@ -430,7 +430,7 @@ public void T100() { // { int a; if (fFalse && G(out a)) No(); else F(a); } // Error Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(129, 55), // (130,50): error CS0165: Use of unassigned local variable 'a' - // { int a; if (fFalse && G(out a)) No(); F(a); } // Error + // { int a; if (fFalse && G(out a)) No(); F(a); } // Error Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(130, 50), // (131,61): error CS0165: Use of unassigned local variable 'a' // { int a; if (fFalse && G(out a)) No(); else No(); F(a); } // Error @@ -503,7 +503,7 @@ public void T100() { // { int a; if (fTrue || G(out a)) F(a); } // Error Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(163, 43), // (164,43): error CS0165: Use of unassigned local variable 'a' - // { int a; if (fTrue || G(out a)) F(a); else No(); } // Error + // { int a; if (fTrue || G(out a)) F(a); else No(); } // Error Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(164, 43), // Note: Dev10 spuriously reports (165,56,165,57): error CS0165: Use of unassigned local variable 'a' @@ -512,7 +512,7 @@ public void T100() { // { int a; if (fTrue || G(out a)) No(); F(a); } // Error Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(166, 49), // (167,60): error CS0165: Use of unassigned local variable 'a' - // { int a; if (fTrue || G(out a)) No(); else No(); F(a); } // Error + // { int a; if (fTrue || G(out a)) No(); else No(); F(a); } // Error Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(167, 60) // Note: Dev10 spuriously reports (168,66,168,67): error CS0165: Use of unassigned local variable 'a' @@ -634,7 +634,7 @@ public void T120() { if (f) { int a; while (fTrue || G(out a)) F(a); } // Error, unreachable expression if (f) { int a; while (fTrue || G(out a)) No(); F(a); } // Error, unreachable expression, not statement - // Assigned + // Assigned if (f) { int a; while (fFalse || G(out a)) F(a); } if (f) { int a; while (fFalse || G(out a)) No(); F(a); } } @@ -768,7 +768,7 @@ public void T130() { // Unassigned. if (f) { int a; do No(); while (fFalse && G(out a)); F(a); } // Error - // + // if (f) { int a; do No(); while (f || G(out a)); F(a); } // Assigned after false if (f) { int a; do No(); while (fTrue || G(out a)); F(a); } // unreachable expr, unassigned if (f) { int a; do No(); while (fFalse || G(out a)); F(a); } // Assigned @@ -816,7 +816,7 @@ public void T131() { // if (f) { int a; do { break; F(a); } while (f); } // Unreachable Diagnostic(ErrorCode.WRN_UnreachableCode, "F"), - // NOTE: By design, we will not match dev10's report of + // NOTE: By design, we will not match dev10's report of // (77,44,77,48): warning CS0162: Unreachable code detected // See DevDiv #13696. @@ -855,7 +855,7 @@ bool F() } "; - // NOTE: By design, we will not match dev10's report of + // NOTE: By design, we will not match dev10's report of // warning CS0162: Unreachable code detected // See DevDiv #13696. CreateCompilation(source).VerifyDiagnostics(); @@ -1359,7 +1359,7 @@ public void T370() { { int a; F(f ? 1 : 2); F(a); } // Error { int a; F(fFalse ? a : 2); } // no DA error; expr is not reachable - { int a; F(fFalse ? 1 : a); } // + { int a; F(fFalse ? 1 : a); } // { int a; F(fTrue ? a : 2); } // Error - should it also be unreachable? { int a; F(fTrue ? 1 : a); } // BUG: Spec says error. Should this be unreachable? @@ -1535,7 +1535,7 @@ public void T370() { // { int a; F(f ? 1 : 2); F(a); } // Error Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), // (164,33): error CS0165: Use of unassigned local variable 'a' - // { int a; F(fFalse ? 1 : a); } // + // { int a; F(fFalse ? 1 : a); } // Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a"), // (166,28): error CS0165: Use of unassigned local variable 'a' // { int a; F(fTrue ? a : 2); } // Error - should it also be unreachable? @@ -2875,6 +2875,1135 @@ void M4(bool b) Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(35, 15)); } + [Theory] + [InlineData("true", "false")] + [InlineData("!false", "!true")] + [InlineData("(true || false)", "(true && false)")] + public void EqualsBoolConstant_01(string @true, string @false) + { + var source = @" +#nullable enable + +class C +{ + public bool M0(out int x, out int y) { x = 42; y = 42; return true; } + + public void M1(bool b) + { + int x, y; + _ = ((b && M0(out x, out y)) == " + @true + @") + ? x.ToString() + : y.ToString(); // 1 + } + + public void M2(bool b) + { + int x, y; + _ = ((b && M0(out x, out y)) != " + @true + @") + ? x.ToString() // 2 + : y.ToString(); + } + + public void M3(bool b) + { + int x, y; + _ = ((b && M0(out x, out y)) == " + @false + @") + ? x.ToString() // 3 + : y.ToString(); + } + + public void M4(bool b) + { + int x, y; + _ = ((b && M0(out x, out y)) != " + @false + @") + ? x.ToString() + : y.ToString(); // 4 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(13, 15), + // (20,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(20, 15), + // (28,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(28, 15), + // (37,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(37, 15) + ); + } + + [Theory] + [InlineData("true", "false")] + [InlineData("!false", "!true")] + [InlineData("(true || false)", "(true && false)")] + public void EqualsBoolConstant_02(string @true, string @false) + { + var source = @" +#nullable enable + +class C +{ + public bool M0(out int x, out int y) { x = 42; y = 42; return true; } + + public void M1(bool b) + { + int x, y; + _ = (" + @true + @" == (b && M0(out x, out y))) + ? x.ToString() + : y.ToString(); // 1 + } + + public void M2(bool b) + { + int x, y; + _ = (" + @true + @" != (b && M0(out x, out y))) + ? x.ToString() // 2 + : y.ToString(); + } + + public void M3(bool b) + { + int x, y; + _ = (" + @false + @" == (b && M0(out x, out y))) + ? x.ToString() // 3 + : y.ToString(); + } + + public void M4(bool b) + { + int x, y; + _ = (" + @false + @" != (b && M0(out x, out y))) + ? x.ToString() + : y.ToString(); // 4 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(13, 15), + // (20,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(20, 15), + // (28,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(28, 15), + // (37,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(37, 15) + ); + } + + [Fact] + public void EqualsBoolConstant_03() + { + var source = @" +#nullable enable + +class C +{ + public bool M0(out int x, out int y) { x = 42; y = 42; return true; } + + public void M1(bool b) + { + int x, y; + _ = ((b && M0(out x, out y)) == true != false) + ? x.ToString() + : y.ToString(); // 1 + } + + public void M2(bool b) + { + int x, y; + _ = (false == (b && M0(out x, out y)) != true) + ? x.ToString() + : y.ToString(); // 2 + } + + public void M3(bool b) + { + int x, y; + _ = (true == false != (b && M0(out x, out y))) + ? x.ToString() + : y.ToString(); // 3 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(13, 15), + // (21,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(21, 15), + // (29,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(29, 15) + ); + } + + [Fact] + public void EqualsBoolConstant_04() + { + var source = @" +#nullable enable + +class C +{ + void M1(object obj) + { + _ = (obj is string x and string y == true) + ? x.ToString() + : y.ToString(); // 1 + } + + void M2(object obj) + { + _ = (obj is string x and string y == false) + ? x.ToString() // 2 + : y.ToString(); + } + + void M3(object obj) + { + _ = (obj is string x and string y != true) + ? x.ToString() // 3 + : y.ToString(); + } + + void M4(object obj) + { + _ = (obj is string x and string y != false) + ? x.ToString() + : y.ToString(); // 4 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (10,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(10, 15), + // (16,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(16, 15), + // (23,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(23, 15), + // (31,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(31, 15) + ); + } + + [Theory] + [InlineData("b")] + [InlineData("true")] + [InlineData("false")] + public void EqualsCondAccess_01(string operand) + { + var source = @" +#nullable enable + +class C +{ + public bool M0(out int x, out int y) { x = 42; y = 42; return true; } + + public void M1(C? c, bool b) + { + int x, y; + _ = c?.M0(out x, out y) == " + operand + @" + ? x.ToString() + : y.ToString(); // 1 + } + + public void M2(C? c, bool b) + { + int x, y; + _ = c?.M0(out x, out y) != " + operand + @" + ? x.ToString() // 2 + : y.ToString(); + } + + public void M3(C? c, bool b) + { + int x, y; + _ = " + operand + @" == c?.M0(out x, out y) + ? x.ToString() + : y.ToString(); // 3 + } + + public void M4(C? c, bool b) + { + int x, y; + _ = " + operand + @" != c?.M0(out x, out y) + ? x.ToString() // 4 + : y.ToString(); + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(13, 15), + // (20,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(20, 15), + // (29,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(29, 15), + // (36,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(36, 15) + ); + } + + [Theory] + [InlineData("i")] + [InlineData("42")] + public void EqualsCondAccess_02(string operand) + { + var source = @" +#nullable enable + +class C +{ + public int M0(out int x, out int y) { x = 1; y = 2; return 0; } + + public void M1(C? c, int i) + { + int x, y; + _ = c?.M0(out x, out y) == " + operand + @" + ? x.ToString() + : y.ToString(); // 1 + } + + public void M2(C? c, int i) + { + int x, y; + _ = c?.M0(out x, out y) != " + operand + @" + ? x.ToString() // 2 + : y.ToString(); + } + + public void M3(C? c, int i) + { + int x, y; + _ = " + operand + @" == c?.M0(out x, out y) + ? x.ToString() + : y.ToString(); // 3 + } + + public void M4(C? c, int i) + { + int x, y; + _ = " + operand + @" != c?.M0(out x, out y) + ? x.ToString() // 4 + : y.ToString(); + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(13, 15), + // (20,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(20, 15), + // (29,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(29, 15), + // (36,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(36, 15) + ); + } + + [Theory] + [InlineData("object?")] + [InlineData("int?")] + [InlineData("bool?")] + public void EqualsCondAccess_03(string returnType) + { + var source = @" +#nullable enable + +class C +{ + public " + returnType + @" M0(out int x, out int y) { x = 42; y = 42; return null; } + + public void M1(C? c) + { + int x, y; + _ = c?.M0(out x, out y) != null + ? x.ToString() + : y.ToString(); // 1 + } + + public void M2(C? c) + { + int x, y; + _ = c?.M0(out x, out y) == null + ? x.ToString() // 2 + : y.ToString(); + } + + public void M3(C? c) + { + int x, y; + _ = null != c?.M0(out x, out y) + ? x.ToString() + : y.ToString(); // 3 + } + + public void M4(C? c) + { + int x, y; + _ = null == c?.M0(out x, out y) + ? x.ToString() // 4 + : y.ToString(); + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(13, 15), + // (20,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(20, 15), + // (29,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(29, 15), + // (36,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(36, 15) + ); + } + + [Fact] + public void EqualsCondAccess_04() + { + var source = @" +#nullable enable + +class C +{ + public bool M0(out int x, out int y) { x = 42; y = 42; return false; } + + public void M1(C? c, bool b) + { + int x1, y1, x2, y2; + _ = c?.M0(out x1, out y1) == c!.M0(out x2, out y2) + ? x1.ToString() + x2.ToString() + : y1.ToString() + y2.ToString(); // 1 + } + + public void M2(C? c, bool b) + { + int x1, y1, x2, y2; + _ = c?.M0(out x1, out y1) != c!.M0(out x2, out y2) + ? x1.ToString() + x2.ToString() // 2 + : y1.ToString() + y2.ToString(); + } + + public void M3(C? c, bool b) + { + int x1, y1, x2, y2; + _ = c!.M0(out x2, out y2) == c?.M0(out x1, out y1) + ? x1.ToString() + x2.ToString() + : y1.ToString() + y2.ToString(); // 3 + } + + public void M4(C? c, bool b) + { + int x1, y1, x2, y2; + _ = c!.M0(out x2, out y2) != c?.M0(out x1, out y1) + ? x1.ToString() + x2.ToString() // 4 + : y1.ToString() + y2.ToString(); + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,15): error CS0165: Use of unassigned local variable 'y1' + // : y1.ToString() + y2.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y1").WithArguments("y1").WithLocation(13, 15), + // (20,15): error CS0165: Use of unassigned local variable 'x1' + // ? x1.ToString() + x2.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x1").WithArguments("x1").WithLocation(20, 15), + // (29,15): error CS0165: Use of unassigned local variable 'y1' + // : y1.ToString() + y2.ToString(); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y1").WithArguments("y1").WithLocation(29, 15), + // (36,15): error CS0165: Use of unassigned local variable 'x1' + // ? x1.ToString() + x2.ToString() // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x1").WithArguments("x1").WithLocation(36, 15) + ); + } + + [Fact] + public void EqualsCondAccess_05() + { + var source = @" +#nullable enable + +class C +{ + public static bool operator ==(C? left, C? right) => false; + public static bool operator !=(C? left, C? right) => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public C? M0(out int x, out int y) { x = 42; y = 42; return this; } + + public void M1(C? c) + { + int x, y; + _ = c?.M0(out x, out y) != null + ? x.ToString() // 1 + : y.ToString(); // 2 + } + + public void M2(C? c) + { + int x, y; + _ = c?.M0(out x, out y) == null + ? x.ToString() // 3 + : y.ToString(); // 4 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (17,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(17, 15), + // (18,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(18, 15), + // (25,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(25, 15), + // (26,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(26, 15) + ); + } + + [Fact] + public void EqualsCondAccess_06() + { + var source = @" +#nullable enable + +class C +{ + public C? M0(out int x, out int y) { x = 42; y = 42; return this; } + + public void M1(C c) + { + int x1, y1, x2, y2; + _ = c.M0(out x1, out y1)?.M0(out x2, out y2) != null + ? x1.ToString() + x2.ToString() + : y1.ToString() + y2.ToString(); // 1 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,31): error CS0165: Use of unassigned local variable 'y2' + // : y1.ToString() + y2.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y2").WithArguments("y2").WithLocation(13, 31) + ); + } + + [Fact] + public void EqualsCondAccess_07() + { + var source = @" +#nullable enable + +struct S +{ + public static bool operator ==(S left, S right) => false; + public static bool operator !=(S left, S right) => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public S M0(out int x, out int y) { x = 42; y = 42; return this; } + + public void M1(S? s) + { + int x, y; + _ = s?.M0(out x, out y) != null + ? x.ToString() + : y.ToString(); // 1 + } + + public void M2(S? s) + { + int x, y; + _ = s?.M0(out x, out y) == null + ? x.ToString() // 2 + : y.ToString(); + } + + public void M3(S? s) + { + int x, y; + _ = null != s?.M0(out x, out y) + ? x.ToString() + : y.ToString(); // 3 + } + + public void M4(S? s) + { + int x, y; + _ = null == s?.M0(out x, out y) + ? x.ToString() // 4 + : y.ToString(); + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (18,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(18, 15), + // (25,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(25, 15), + // (34,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(34, 15), + // (41,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(41, 15) + ); + } + + [Fact] + public void EqualsCondAccess_08() + { + var source = @" +#nullable enable + +struct S +{ + public static bool operator ==(S left, S right) => false; + public static bool operator !=(S left, S right) => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public S M0(out int x, out int y) { x = 42; y = 42; return this; } + + public void M1(S? s) + { + int x, y; + _ = s?.M0(out x, out y) != new S() + ? x.ToString() // 1 + : y.ToString(); + } + + public void M2(S? s) + { + int x, y; + _ = s?.M0(out x, out y) == new S() + ? x.ToString() + : y.ToString(); // 2 + } + + public void M3(S? s) + { + int x, y; + _ = new S() != s?.M0(out x, out y) + ? x.ToString() // 3 + : y.ToString(); + } + + public void M4(S? s) + { + int x, y; + _ = new S() == s?.M0(out x, out y) + ? x.ToString() + : y.ToString(); // 4 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (17,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(17, 15), + // (26,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(26, 15), + // (33,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(33, 15), + // (42,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(42, 15) + ); + } + + [Fact] + public void EqualsCondAccess_09() + { + var source = @" +#nullable enable + +struct S +{ + public static bool operator ==(S left, S right) => false; + public static bool operator !=(S left, S right) => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public S M0(out int x, out int y) { x = 42; y = 42; return this; } + + public void M1(S? s) + { + int x, y; + _ = s?.M0(out x, out y) != s + ? x.ToString() // 1 + : y.ToString(); // 2 + } + + public void M2(S? s) + { + int x, y; + _ = s?.M0(out x, out y) == s + ? x.ToString() // 3 + : y.ToString(); // 4 + } + + public void M3(S? s) + { + int x, y; + _ = s != s?.M0(out x, out y) + ? x.ToString() // 5 + : y.ToString(); // 6 + } + + public void M4(S? s) + { + int x, y; + _ = s == s?.M0(out x, out y) + ? x.ToString() // 7 + : y.ToString(); // 8 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (17,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(17, 15), + // (18,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(18, 15), + // (25,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(25, 15), + // (26,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(26, 15), + // (33,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 5 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(33, 15), + // (34,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 6 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(34, 15), + // (41,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 7 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(41, 15), + // (42,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 8 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(42, 15) + ); + } + + [Theory] + [InlineData("S? left, S? right")] + [InlineData("S? left, S right")] + public void EqualsCondAccess_10(string operatorParameters) + { + var source = @" +#nullable enable + +struct S +{ + public static bool operator ==(" + operatorParameters + @") => false; + public static bool operator !=(" + operatorParameters + @") => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public S M0(out int x, out int y) { x = 42; y = 42; return this; } + + public void M1(S? s) + { + int x, y; + _ = s?.M0(out x, out y) != new S() + ? x.ToString() // 1 + : y.ToString(); // 2 + } + + public void M2(S? s) + { + int x, y; + _ = s?.M0(out x, out y) == new S() + ? x.ToString() // 3 + : y.ToString(); // 4 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (17,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(17, 15), + // (18,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(18, 15), + // (25,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(25, 15), + // (26,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(26, 15) + ); + } + + [Fact] + public void EqualsCondAccess_11() + { + var source = @" +#nullable enable + +struct T +{ + public static implicit operator S(T t) => new S(); + public T M0(out int x, out int y) { x = 42; y = 42; return this; } +} + +struct S +{ + public static bool operator ==(S left, S right) => false; + public static bool operator !=(S left, S right) => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public void M1(T? t) + { + int x, y; + _ = t?.M0(out x, out y) != new S() + ? x.ToString() // 1 + : y.ToString(); + } + + public void M2(T? t) + { + int x, y; + _ = t?.M0(out x, out y) == new S() + ? x.ToString() + : y.ToString(); // 2 + } + + public void M3(T? t) + { + int x, y; + _ = new S() != t?.M0(out x, out y) + ? x.ToString() // 3 + : y.ToString(); + } + + public void M4(T? t) + { + int x, y; + _ = new S() == t?.M0(out x, out y) + ? x.ToString() + : y.ToString(); // 4 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (21,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(21, 15), + // (30,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(30, 15), + // (37,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(37, 15), + // (46,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(46, 15) + ); + } + + [Fact] + public void EqualsCondAccess_12() + { + var source = @" +#nullable enable + +class C +{ + int? M0(object obj) => null; + + void M1(C? c, object? obj) + { + int x, y; + _ = (c?.M0(x = y = 0) == (int?)obj) + ? x.ToString() // 1 + : y.ToString(); // 2 + } + + void M2(C? c, object? obj) + { + int x, y; + _ = (c?.M0(x = y = 0) == (int?)null) + ? x.ToString() // 3 + : y.ToString(); + } + + void M3(C? c, object? obj) + { + int x, y; + _ = (c?.M0(x = y = 0) == null) + ? x.ToString() // 4 + : y.ToString(); + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (12,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(12, 15), + // (13,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(13, 15), + // (20,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(20, 15), + // (28,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(28, 15) + ); + } + + [Fact] + public void EqualsCondAccess_13() + { + var source = @" +#nullable enable + +class C +{ + long? M0(object obj) => null; + void M(C? c, int i) + { + int x, y; + _ = (c?.M0(x = y = 0) == i) + ? x.ToString() + : y.ToString(); // 2 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (12,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(12, 15) + ); + } + + [Fact] + public void EqualsCondAccess_14() + { + var source = @" +#nullable enable + +class C +{ + long? M0(object obj) => null; + void M(C? c, int? i) + { + int x, y; + _ = (c?.M0(x = y = 0) == i) + ? x.ToString() // 1 + : y.ToString(); // 2 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (11,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(11, 15), + // (12,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(12, 15) + ); + } + + [Fact] + public void EqualsCondAccess_15() + { + var source = @" +#nullable enable + +class C +{ + C M0(object obj) => this; + void M(C? c) + { + int x, y; + _ = ((object?)c?.M0(x = y = 0) != null) + ? x.ToString() + : y.ToString(); // 1 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (12,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(12, 15) + ); + } + + [Fact] + public void EqualsCondAccess_16() + { + var source = @" +#nullable enable + +class C +{ + void M() + { + int x, y; + _ = ""a""?.Equals(x = y = 0) == true + ? x.ToString() + : y.ToString(); + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + ); + } + + [Fact] + public void EqualsCondAccess_17() + { + var source = @" +#nullable enable + +class C +{ + void M(C? c) + { + int x, y; + _ = (c?.Equals(x = 0), c?.Equals(y = 0)) == (true, true) + ? x.ToString() // 1 + : y.ToString(); // 2 + } +} +"; + // This could be made to work (i.e. removing diagnostic 1) but isn't a high priority scenario. + // The corresponding scenario in nullable also doesn't work: + // void M(string? x, string? y) + // { + // if ((x, y) == ("a", "b")) + // { + // x.ToString(); // warning + // y.ToString(); // warning + // } + // } + // https://github.com/dotnet/roslyn/issues/50980 + CreateCompilation(source).VerifyDiagnostics( + // (10,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(10, 15), + // (11,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(11, 15) + ); + } + + [Fact] + public void EqualsCondAccess_18() + { + var source = @" +#nullable enable + +class C +{ + public static bool operator ==(C? left, C? right) => false; + public static bool operator !=(C? left, C? right) => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public C M0(out int x, out int y) { x = 42; y = 42; return this; } + + public void M1(C? c) + { + int x, y; + _ = c?.M0(out x, out y) != null + ? x.ToString() // 1 + : y.ToString(); // 2 + } + + public void M2(C? c) + { + int x, y; + _ = c?.M0(out x, out y) == null + ? x.ToString() // 3 + : y.ToString(); // 4 + } + + public void M3(C? c) + { + int x, y; + _ = null != c?.M0(out x, out y) + ? x.ToString() // 5 + : y.ToString(); // 6 + } + + public void M4(C? c) + { + int x, y; + _ = null == c?.M0(out x, out y) + ? x.ToString() // 7 + : y.ToString(); // 8 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (17,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(17, 15), + // (18,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(18, 15), + // (25,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(25, 15), + // (26,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(26, 15), + // (33,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 5 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(33, 15), + // (34,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 6 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(34, 15), + // (41,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 7 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(41, 15), + // (42,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 8 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(42, 15) + ); + } + [WorkItem(545352, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545352")] [Fact] public void UseDefViolationInDelegateInSwitchWithGoto() @@ -3289,7 +4418,7 @@ static T F(System.ValueType o) public void AssignedInFinallyUsedInTry() { var source = -@" +@" public class Program { static void Main(string[] args) @@ -3299,7 +4428,7 @@ static void Main(string[] args) public static void Test() { - object obj; + object obj; try { From 667fb28c8d8f0b0f8aaf87a06fe61dfdff3dec76 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 20 Apr 2021 10:48:45 -0700 Subject: [PATCH 4/9] Handle 'is' operators and patterns in definite assignment (#52616) --- .../Portable/FlowAnalysis/AbstractFlowPass.cs | 126 +++- .../Portable/FlowAnalysis/NullableWalker.cs | 4 +- .../FlowAnalysis/NullableWalker_Patterns.cs | 2 +- .../Test/Semantic/FlowAnalysis/FlowTests.cs | 635 ++++++++++++++++++ 4 files changed, 762 insertions(+), 5 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index 2acef1c397f5c..5d23b6af38b52 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -962,11 +962,42 @@ public override BoundNode VisitPassByCopy(BoundPassByCopy node) public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node) { Debug.Assert(!IsConditionalState); - VisitRvalue(node.Expression); bool negated = node.Pattern.IsNegated(out var pattern); Debug.Assert(negated == node.IsNegated); + if (VisitPossibleConditionalAccess(node.Expression, out var stateWhenNotNull)) + { + Debug.Assert(!IsConditionalState); + switch (isTopLevelNonNullTest(pattern)) + { + case true: + SetConditionalState(stateWhenNotNull, State); + break; + case false: + SetConditionalState(State, stateWhenNotNull); + break; + } + } + else if (IsConditionalState) + { + // Patterns which only match a single boolean value should propagate conditional state + // for example, `(a != null && a.M(out x)) is true` should have the same conditional state as `(a != null && a.M(out x))`. + if (isBoolTest(pattern) is bool value) + { + if (!value) + { + SetConditionalState(StateWhenFalse, StateWhenTrue); + } + } + else + { + // Patterns which match more than a single boolean value cannot propagate conditional state + // for example, `(a != null && a.M(out x)) is bool b` should not have conditional state + Unsplit(); + } + } + VisitPattern(pattern); var reachableLabels = node.DecisionDag.ReachableLabels; if (!reachableLabels.Contains(node.WhenTrueLabel)) @@ -986,6 +1017,88 @@ public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node } return node; + + // Returns `true` if the pattern only matches if the top-level input is not null. + // Returns `false` if the pattern only matches if the top-level input is null. + // Otherwise, returns `null`. + static bool? isTopLevelNonNullTest(BoundPattern pattern) + { + switch (pattern) + { + case BoundTypePattern: + case BoundRecursivePattern: + case BoundITuplePattern: + case BoundRelationalPattern: + case BoundDeclarationPattern { IsVar: false }: + case BoundConstantPattern { ConstantValue: { IsNull: false } }: + return true; + case BoundConstantPattern { ConstantValue: { IsNull: true } }: + return false; + case BoundNegatedPattern negated: + return !isTopLevelNonNullTest(negated.Negated); + case BoundBinaryPattern binary: + if (binary.Disjunction) + { + // `a?.b(out x) is null or C` + // both subpatterns must have the same null test for the test to propagate out + var leftNullTest = isTopLevelNonNullTest(binary.Left); + return leftNullTest is null ? null : + leftNullTest != isTopLevelNonNullTest(binary.Right) ? null : + leftNullTest; + } + + // `a?.b(out x) is not null and var c` + // if any pattern performs a test, we know that test applies at the top level + // note that if the tests are different, e.g. `null and not null`, + // the pattern is a contradiction, so we expect an error diagnostic + return isTopLevelNonNullTest(binary.Left) ?? isTopLevelNonNullTest(binary.Right); + case BoundDeclarationPattern { IsVar: true }: + case BoundDiscardPattern: + return null; + default: + throw ExceptionUtilities.UnexpectedValue(pattern.Kind); + } + } + + // Returns `true` if the pattern only matches a `true` input. + // Returns `false` if the pattern only matches a `false` input. + // Otherwise, returns `null`. + static bool? isBoolTest(BoundPattern pattern) + { + switch (pattern) + { + case BoundConstantPattern { ConstantValue: { IsBoolean: true, BooleanValue: var boolValue } }: + return boolValue; + case BoundNegatedPattern negated: + return !isBoolTest(negated.Negated); + case BoundBinaryPattern binary: + if (binary.Disjunction) + { + // `(a != null && a.b(out x)) is true or true` matches `true` + // `(a != null && a.b(out x)) is true or false` matches any boolean + // both subpatterns must have the same bool test for the test to propagate out + var leftNullTest = isBoolTest(binary.Left); + return leftNullTest is null ? null : + leftNullTest != isBoolTest(binary.Right) ? null : + leftNullTest; + } + + // `(a != null && a.b(out x)) is true and true` matches `true` + // `(a != null && a.b(out x)) is true and var x` matches `true` + // `(a != null && a.b(out x)) is true and false` never matches and is a compile error + return isBoolTest(binary.Left) ?? isBoolTest(binary.Right); + case BoundConstantPattern { ConstantValue: { IsBoolean: false } }: + case BoundDiscardPattern: + case BoundTypePattern: + case BoundRecursivePattern: + case BoundITuplePattern: + case BoundRelationalPattern: + case BoundDeclarationPattern: + return null; + default: + throw ExceptionUtilities.UnexpectedValue(pattern.Kind); + } + } } public virtual void VisitPattern(BoundPattern pattern) @@ -2510,7 +2623,16 @@ public override BoundNode VisitAsOperator(BoundAsOperator node) public override BoundNode VisitIsOperator(BoundIsOperator node) { - VisitRvalue(node.Operand); + if (VisitPossibleConditionalAccess(node.Operand, out var stateWhenNotNull)) + { + Debug.Assert(!IsConditionalState); + SetConditionalState(stateWhenNotNull, State); + } + else + { + // `(a && b.M(out x)) is bool` should discard conditional state from LHS + Unsplit(); + } return null; } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index e33f540b0a4a2..c66241a16235e 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -9105,7 +9105,7 @@ private TypeWithState InferResultNullabilityOfBinaryLogicalOperator(BoundExpress var operand = node.Operand; var typeExpr = node.TargetType; - var result = base.VisitIsOperator(node); + VisitRvalue(operand); Debug.Assert(node.Type.SpecialType == SpecialType.System_Boolean); Split(); @@ -9117,7 +9117,7 @@ private TypeWithState InferResultNullabilityOfBinaryLogicalOperator(BoundExpress VisitTypeExpression(typeExpr); SetNotNullResult(node); - return result; + return null; } public override BoundNode? VisitAsOperator(BoundAsOperator node) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs index 8dfa376a19920..d90616be8be34 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs @@ -57,7 +57,7 @@ public override BoundNode VisitRecursivePattern(BoundRecursivePattern node) public override BoundNode VisitConstantPattern(BoundConstantPattern node) { - Visit(node.Value); + VisitRvalue(node.Value); return null; } diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs index 0fca9adc9fb06..591e04adcc780 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs @@ -4004,6 +4004,641 @@ public void M4(C? c) ); } + [Fact] + public void IsBool() + { + var source = @" +#nullable enable + +class C +{ + bool M0(object obj) => false; + + void M1(bool b) + { + int x, y; + _ = (b && M0(x = y = 0)) is bool // 1 + ? x.ToString() // 2 + : y.ToString(); // 3 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (11,13): warning CS0183: The given expression is always of the provided ('bool') type + // _ = (b && M0(x = y = 0)) is bool // 1 + Diagnostic(ErrorCode.WRN_IsAlwaysTrue, "(b && M0(x = y = 0)) is bool").WithArguments("bool").WithLocation(11, 13), + // (12,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(12, 15), + // (13,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(13, 15) + ); + } + + [Theory] + [InlineData("true", "false")] + [InlineData("!false", "!true")] + [InlineData("(true || false)", "(true && false)")] + public void IsBoolConstant_01(string @true, string @false) + { + var source = @" +#nullable enable + +class C +{ + bool M0(object obj) => false; + + void M1(bool b) + { + int x, y; + _ = (b && M0(x = y = 0)) is " + @true + @" + ? x.ToString() + : y.ToString(); // 1 + } + + void M2(bool b) + { + int x, y; + _ = (b && M0(x = y = 0)) is " + @false + @" + ? x.ToString() // 2 + : y.ToString(); + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(13, 15), + // (20,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(20, 15) + ); + } + + [Fact] + public void IsBoolConstant_02() + { + var source = @" +#nullable enable + +class C +{ + bool M0(object obj) => false; + + void M1(bool b) + { + int x, y; + _ = (b && M0(x = y = 0)) is var z + ? x.ToString() // 1 + : y.ToString(); // unreachable + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (12,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(12, 15) + ); + } + + [Fact] + public void IsBoolConstant_03() + { + var source = @" +#nullable enable +#pragma warning disable 8794 // An expression always matches the provided pattern + +class C +{ + bool M0(object obj) => false; + + void M1(bool b) + { + int x, y; + _ = (b && M0(x = y = 0)) is not true + ? x.ToString() // 1 + : y.ToString(); + } + + void M2(bool b) + { + int x, y; + _ = (b && M0(x = y = 0)) is true or false + ? x.ToString() // 2 + : y.ToString(); // unreachable + } + + void M3(bool b) + { + int x, y; + _ = (b && M0(x = y = 0)) is true and false // 3 + ? x.ToString() // unreachable + : y.ToString(); // 4 + } + + void M4(bool b) + { + int x, y; + _ = (b && M0(x = y = 0)) is true or true + ? x.ToString() + : y.ToString(); // 5 + } + + void M5(bool b) + { + int x, y; + _ = (b && M0(x = y = 0)) is true and true + ? x.ToString() + : y.ToString(); // 6 + } + + void M6(bool b) + { + int x, y; + _ = (b && M0(x = y = 0)) is false or false + ? x.ToString() // 7 + : y.ToString(); + } + + void M7(bool b) + { + int x, y; + _ = (b && M0(x = y = 0)) is false and var z + ? x.ToString() // 8 + : y.ToString(); + } + + void M8(bool b) + { + int x, y; + _ = (b && M0(x = y = 0)) is var z or true // 9 + ? x.ToString() // 10 + : y.ToString(); // unreachable + } + + void M9(bool b) + { + int x, y; + _ = (b && M0(x = y = 0)) is not (false and false) + ? x.ToString() + : y.ToString(); // 11 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(13, 15), + // (21,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(21, 15), + // (28,13): error CS8518: An expression of type 'bool' can never match the provided pattern. + // _ = (b && M0(x = y = 0)) is true and false // 3 + Diagnostic(ErrorCode.ERR_IsPatternImpossible, "(b && M0(x = y = 0)) is true and false").WithArguments("bool").WithLocation(28, 13), + // (30,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(30, 15), + // (38,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 5 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(38, 15), + // (46,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 6 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(46, 15), + // (53,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 7 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(53, 15), + // (61,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 8 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(61, 15), + // (68,41): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // _ = (b && M0(x = y = 0)) is var z or true // 9 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "z").WithLocation(68, 41), + // (69,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 10 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(69, 15), + // (78,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 11 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(78, 15) + ); + } + + [Fact] + public void IsCondAccess_01() + { + var source = @" +#nullable enable + +class C +{ + C M0(object obj) => this; + + void M1(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is C + ? x.ToString() + : y.ToString(); // 1 + } + + void M2(C? c) + { + int x, y; + _ = c?.Equals(x = y = 0) is bool + ? x.ToString() + : y.ToString(); // 2 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(13, 15), + // (21,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(21, 15) + ); + } + + [Fact] + public void IsCondAccess_02() + { + var source = @" +#nullable enable + +class C +{ + C M0(object obj) => this; + + void M1(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is (_) + ? x.ToString() // 1 + : y.ToString(); // unreachable + } + + void M2(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is var z + ? x.ToString() // 2 + : y.ToString(); // unreachable + } + + void M3(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is { } + ? x.ToString() + : y.ToString(); // 3 + } + + void M4(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is { } c1 + ? x.ToString() + : y.ToString(); // 4 + } + + void M5(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is C c1 + ? x.ToString() + : y.ToString(); // 5 + } + + void M6(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is not null + ? x.ToString() + : y.ToString(); // 6 + } + + void M7(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is not C + ? x.ToString() // 7 + : y.ToString(); + } + + void M8(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is null + ? x.ToString() // 8 + : y.ToString(); + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (12,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(12, 15), + // (20,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(20, 15), + // (29,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(29, 15), + // (37,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(37, 15), + // (45,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 5 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(45, 15), + // (53,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 6 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(53, 15), + // (60,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 7 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(60, 15), + // (68,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 8 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(68, 15) + ); + } + + [Fact] + public void IsCondAccess_03() + { + var source = @" +#nullable enable + +class C +{ + (C, C) M0(object obj) => (this, this); + + void M1(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is (_, _) + ? x.ToString() + : y.ToString(); // 1 + } + + void M2(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is not null + ? x.ToString() + : y.ToString(); // 2 + } + + void M3(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is (not null, null) + ? x.ToString() + : y.ToString(); // 3 + } + + void M4(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is null + ? x.ToString() // 4 + : y.ToString(); + } + + void M5(C? c) + { + int x, y; + _ = (c?.M0(x = 0), c?.M0(y = 0)) is (not null, not null) + ? x.ToString() // 5 + : y.ToString(); // 6 + } +} +"; + // note: "state when not null" is not tracked when pattern matching against tuples containing conditional accesses. + CreateCompilation(source).VerifyDiagnostics( + // (13,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(13, 15), + // (21,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(21, 15), + // (29,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(29, 15), + // (36,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(36, 15), + // (44,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 5 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(44, 15), + // (45,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 6 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(45, 15) + ); + } + + [Fact] + public void IsCondAccess_04() + { + var source = @" +#nullable enable +#pragma warning disable 8794 // An expression always matches the provided pattern + +class C +{ + C M0(object obj) => this; + + void M1(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is null or not null + ? x.ToString() // 1 + : y.ToString(); // unreachable + } + + void M2(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is C or null + ? x.ToString() // 2 + : y.ToString(); // unreachable + } + + void M3(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is not null and C + ? x.ToString() + : y.ToString(); // 3 + } + + void M4(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is null + ? x.ToString() // 4 + : y.ToString(); + } + + void M5(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is not (C or { }) + ? x.ToString() // 5 + : y.ToString(); + } + + void M6(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is _ and C + ? x.ToString() + : y.ToString(); // 6 + } + + void M7(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is C and _ + ? x.ToString() + : y.ToString(); // 7 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(13, 15), + // (21,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(21, 15), + // (30,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(30, 15), + // (37,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(37, 15), + // (45,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 5 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(45, 15), + // (54,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 6 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(54, 15), + // (62,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 7 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(62, 15) + ); + } + + [Theory] + [InlineData("int")] + [InlineData("int?")] + public void IsCondAccess_05(string returnType) + { + var source = @" +#nullable enable + +class C +{ + " + returnType + @" M0(object obj) => 1; + + void M1(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is 1 + ? x.ToString() + : y.ToString(); // 1 + } + + void M2(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is > 10 + ? x.ToString() + : y.ToString(); // 2 + } + + void M3(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is > 10 or < 0 + ? x.ToString() + : y.ToString(); // 3 + } + + void M4(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is 1 or 2 + ? x.ToString() + : y.ToString(); // 4 + } + + void M5(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is null + ? x.ToString() // 5 + : y.ToString(); + } + + void M6(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is not null + ? x.ToString() + : y.ToString(); // 6 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(13, 15), + // (21,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(21, 15), + // (29,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(29, 15), + // (37,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(37, 15), + // (44,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 5 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(44, 15), + // (53,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 6 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(53, 15) + ); + } + + [Fact] + public void IsCondAccess_06() + { + var source = @" +#nullable enable + +class C +{ + int M0(object obj) => 1; + + void M1(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is not null is true + ? x.ToString() + : y.ToString(); // 1 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(13, 15) + ); + } + [WorkItem(545352, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545352")] [Fact] public void UseDefViolationInDelegateInSwitchWithGoto() From 884d730ef5d2aec3fa201c4fb849b36cc96eab17 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 3 May 2021 18:12:18 -0700 Subject: [PATCH 5/9] Improved nullable '??' analysis (#52646) --- .../Portable/FlowAnalysis/AbstractFlowPass.cs | 55 +- .../Portable/FlowAnalysis/NullableWalker.cs | 211 ++- .../Test/Semantic/FlowAnalysis/FlowTests.cs | 57 + .../Semantics/NullableReferenceTypesTests.cs | 1404 +++++++++++++++++ 4 files changed, 1670 insertions(+), 57 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index 5d23b6af38b52..37f38869ce494 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -2697,7 +2697,7 @@ private bool TryVisitConditionalAccess(BoundExpression node, [NotNullWhen(true)] var access = node switch { BoundConditionalAccess ca => ca, - BoundConversion { Conversion: Conversion innerConversion, Operand: BoundConditionalAccess ca } when isAcceptableConversion(ca, innerConversion) => ca, + BoundConversion { Conversion: Conversion innerConversion, Operand: BoundConditionalAccess ca } when CanPropagateStateWhenNotNull(ca, innerConversion) => ca, _ => null }; @@ -2714,26 +2714,29 @@ private bool TryVisitConditionalAccess(BoundExpression node, [NotNullWhen(true)] stateWhenNotNull = default; return false; - // "State when not null" can only propagate out of a conditional access if - // it is not subject to a user-defined conversion whose parameter is not of a non-nullable value type. - static bool isAcceptableConversion(BoundConditionalAccess operand, Conversion conversion) - { - if (conversion.Kind is not (ConversionKind.ImplicitUserDefined or ConversionKind.ExplicitUserDefined)) - { - return true; - } + } - var method = conversion.Method; - Debug.Assert(method is not null); - Debug.Assert(method.ParameterCount is 1); - var param = method.Parameters[0]; - if (operand.Type.IsNullableType() && param.Type.IsNonNullableValueType()) - { - return true; - } + /// + /// "State when not null" can only propagate out of a conditional access if + /// it is not subject to a user-defined conversion whose parameter is not of a non-nullable value type. + /// + protected static bool CanPropagateStateWhenNotNull(BoundConditionalAccess operand, Conversion conversion) + { + if (conversion.Kind is not (ConversionKind.ImplicitUserDefined or ConversionKind.ExplicitUserDefined)) + { + return true; + } - return false; + var method = conversion.Method; + Debug.Assert(method is not null); + Debug.Assert(method.ParameterCount is 1); + var param = method.Parameters[0]; + if (operand.Type.IsNullableType() && param.Type.IsNonNullableValueType()) + { + return true; } + + return false; } /// @@ -2760,8 +2763,19 @@ private void VisitConditionalAccess(BoundConditionalAccess node, out TLocalState if (node.Receiver.ConstantValue != null && !IsConstantNull(node.Receiver)) { - VisitRvalue(node.AccessExpression); - stateWhenNotNull = this.State.Clone(); + // Consider a scenario like `"a"?.M0(x = 1)?.M0(y = 1)`. + // We can "know" that `.M0(x = 1)` was evaluated unconditionally but not `M0(y = 1)`. + // Therefore we do a VisitPossibleConditionalAccess here which unconditionally includes the "after receiver" state in State + // and includes the "after subsequent conditional accesses" in stateWhenNotNull + if (VisitPossibleConditionalAccess(node.AccessExpression, out var innerStateWhenNotNull)) + { + stateWhenNotNull = innerStateWhenNotNull; + } + else + { + Unsplit(); + stateWhenNotNull = this.State.Clone(); + } } else { @@ -2783,7 +2797,6 @@ private void VisitConditionalAccess(BoundConditionalAccess node, out TLocalState VisitRvalue(innerCondAccess.Receiver); expr = innerCondAccess.AccessExpression; - // PROTOTYPE(improved-definite-assignment): Add NullableWalker implementation and tests which exercises this. // The savedState here represents the scenario where 0 or more of the access expressions could have been evaluated. // e.g. after visiting `a?.b(x = null)?.c(x = new object())`, the "state when not null" of `x` is NotNull, but the "state when maybe null" of `x` is MaybeNull. Join(ref savedState, ref State); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index c66241a16235e..2f02ef2aa5562 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -3992,6 +3992,7 @@ private static void MarkSlotsAsNotNull(ArrayBuilder slots, ref LocalState s } } + // PROTOTYPE(improved-definite-assignment): eventually, GetSlotsToMarkAsNotNullable should be removed private void LearnFromNonNullTest(BoundExpression expression, ref LocalState state) { if (expression.Kind == BoundKind.AwaitableValuePlaceholder) @@ -4158,20 +4159,20 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly BoundExpression leftOperand = node.LeftOperand; BoundExpression rightOperand = node.RightOperand; - TypeWithState leftResult = VisitRvalueWithState(leftOperand); - TypeWithState rightResult; - if (IsConstantNull(leftOperand)) { - rightResult = VisitRvalueWithState(rightOperand); + VisitRvalue(leftOperand); + Visit(rightOperand); + var rightUnconditionalResult = ResultType; // Should be able to use rightResult for the result of the operator but // binding may have generated a different result type in the case of errors. - SetResultType(node, TypeWithState.Create(node.Type, rightResult.State)); + SetResultType(node, TypeWithState.Create(node.Type, rightUnconditionalResult.State)); return null; } - var whenNotNull = this.State.Clone(); - LearnFromNonNullTest(leftOperand, ref whenNotNull); + VisitPossibleConditionalAccess(leftOperand, out var whenNotNull); + TypeWithState leftResult = ResultType; + Unsplit(); LearnFromNullTest(leftOperand, ref this.State); bool leftIsConstant = leftOperand.ConstantValue != null; @@ -4180,21 +4181,23 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly SetUnreachable(); } - rightResult = VisitRvalueWithState(rightOperand); - - Join(ref this.State, ref whenNotNull); + Visit(rightOperand); + TypeWithState rightResult = ResultType; - if (rightOperand.ConstantValue?.IsBoolean ?? false) + if (whenNotNull.IsConditionalState) { Split(); - if (rightOperand.ConstantValue.BooleanValue) - { - StateWhenFalse = whenNotNull; - } - else - { - StateWhenTrue = whenNotNull; - } + } + + if (IsConditionalState) + { + Join(ref StateWhenTrue, ref whenNotNull.IsConditionalState ? ref whenNotNull.StateWhenTrue : ref whenNotNull.State); + Join(ref StateWhenFalse, ref whenNotNull.IsConditionalState ? ref whenNotNull.StateWhenFalse : ref whenNotNull.State); + } + else + { + Debug.Assert(!whenNotNull.IsConditionalState); + Join(ref State, ref whenNotNull.State); } var leftResultType = leftResult.Type; @@ -4259,38 +4262,158 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly } } - public override BoundNode? VisitConditionalAccess(BoundConditionalAccess node) + // PROTOTYPE(improved-definite-assignment): + // this is not used outside 'VisitPossibleConditionalAccess' yet, but is expected to be used + // when refactoring 'VisitBinaryOperatorChildren' + /// + /// Visits a node only if it is a conditional access. + /// Returns 'true' if and only if the node was visited. + /// + private bool TryVisitConditionalAccess(BoundExpression node, out PossiblyConditionalState stateWhenNotNull) + { + var (operand, conversion) = RemoveConversion(node, includeExplicitConversions: true); + if (operand is not BoundConditionalAccess access || !CanPropagateStateWhenNotNull(access, conversion)) + { + stateWhenNotNull = default; + return false; + } + + Unsplit(); + VisitConditionalAccess(access, out stateWhenNotNull); + if (node is BoundConversion boundConversion) + { + var operandType = ResultType; + TypeWithAnnotations explicitType = boundConversion.ConversionGroupOpt?.ExplicitType ?? default; + bool fromExplicitCast = explicitType.HasType; + TypeWithAnnotations targetType = fromExplicitCast ? explicitType : TypeWithAnnotations.Create(boundConversion.Type); + Debug.Assert(targetType.HasType); + var result = VisitConversion(boundConversion, + access, + conversion, + targetType, + operandType, + checkConversion: true, + fromExplicitCast, + useLegacyWarnings: true, + assignmentKind: AssignmentKind.Assignment); + SetResultType(boundConversion, result); + } + Debug.Assert(!IsConditionalState); + return true; + } + + /// + /// Unconditionally visits an expression and returns the "state when not null" for the expression. + /// + private void VisitPossibleConditionalAccess(BoundExpression node, out PossiblyConditionalState stateWhenNotNull) + { + if (!TryVisitConditionalAccess(node, out stateWhenNotNull)) + { + // in case we *didn't* have a conditional access, the only thing we learn in the "state when not null" + // is that the top-level expression was non-null. + Visit(node); + stateWhenNotNull = PossiblyConditionalState.Create(this); + var slot = MakeSlot(node); + if (slot > -1) + { + if (IsConditionalState) + { + LearnFromNonNullTest(slot, ref stateWhenNotNull.StateWhenTrue); + LearnFromNonNullTest(slot, ref stateWhenNotNull.StateWhenFalse); + } + else + { + LearnFromNonNullTest(slot, ref stateWhenNotNull.State); + } + } + } + } + + private void VisitConditionalAccess(BoundConditionalAccess node, out PossiblyConditionalState stateWhenNotNull) { Debug.Assert(!IsConditionalState); var receiver = node.Receiver; - _ = VisitRvalueWithState(receiver); + VisitRvalue(receiver); _currentConditionalReceiverVisitResult = _visitResult; var previousConditionalAccessSlot = _lastConditionalAccessSlot; - var receiverState = this.State.Clone(); - if (IsConstantNull(node.Receiver)) + if (receiver.ConstantValue is { IsNull: false }) { - SetUnreachable(); - _lastConditionalAccessSlot = -1; + // Consider a scenario like `"a"?.M0(x = 1)?.M0(y = 1)`. + // We can "know" that `.M0(x = 1)` was evaluated unconditionally but not `M0(y = 1)`. + // Therefore we do a VisitPossibleConditionalAccess here which unconditionally includes the "after receiver" state in State + // and includes the "after subsequent conditional accesses" in stateWhenNotNull + VisitPossibleConditionalAccess(node.AccessExpression, out stateWhenNotNull); } else { - // In the when-null branch, the receiver is known to be maybe-null. - // In the other branch, the receiver is known to be non-null. - LearnFromNullTest(receiver, ref receiverState); - LearnFromNonNullTest(receiver, ref this.State); - var nextConditionalAccessSlot = MakeSlot(receiver); - if (nextConditionalAccessSlot > 0 && receiver.Type?.IsNullableType() == true) - nextConditionalAccessSlot = GetNullableOfTValueSlot(receiver.Type, nextConditionalAccessSlot, out _); + var savedState = this.State.Clone(); + if (IsConstantNull(receiver)) + { + SetUnreachable(); + _lastConditionalAccessSlot = -1; + } + else + { + // In the when-null branch, the receiver is known to be maybe-null. + // In the other branch, the receiver is known to be non-null. + LearnFromNullTest(receiver, ref savedState); + makeAndAdjustReceiverSlot(receiver); + } + + // We want to preserve stateWhenNotNull from accesses in the same "chain": + // a?.b(out x)?.c(out y); // expected to preserve stateWhenNotNull from both ?.b(out x) and ?.c(out y) + // but not accesses in nested expressions: + // a?.b(out x, c?.d(out y)); // expected to preserve stateWhenNotNull from a?.b(out x, ...) but not from c?.d(out y) + BoundExpression expr = node.AccessExpression; + while (expr is BoundConditionalAccess innerCondAccess) + { + // we assume that non-conditional accesses can never contain conditional accesses from the same "chain". + // that is, we never have to dig through non-conditional accesses to find and handle conditional accesses. + VisitRvalue(innerCondAccess.Receiver); + _currentConditionalReceiverVisitResult = _visitResult; + makeAndAdjustReceiverSlot(innerCondAccess.Receiver); - _lastConditionalAccessSlot = nextConditionalAccessSlot; + // The savedState here represents the scenario where 0 or more of the access expressions could have been evaluated. + // e.g. after visiting `a?.b(x = null)?.c(x = new object())`, the "state when not null" of `x` is NotNull, but the "state when maybe null" of `x` is MaybeNull. + Join(ref savedState, ref State); + + expr = innerCondAccess.AccessExpression; + } + + Debug.Assert(expr is BoundExpression); + Visit(expr); + + expr = node.AccessExpression; + while (expr is BoundConditionalAccess innerCondAccess) + { + // The resulting nullability of each nested conditional access is the same as the resulting nullability of the rightmost access. + SetAnalyzedNullability(innerCondAccess, _visitResult); + expr = innerCondAccess.AccessExpression; + } + Debug.Assert(expr is BoundExpression); + var slot = MakeSlot(expr); + if (slot > -1) + { + if (IsConditionalState) + { + LearnFromNonNullTest(slot, ref StateWhenTrue); + LearnFromNonNullTest(slot, ref StateWhenFalse); + } + else + { + LearnFromNonNullTest(slot, ref State); + } + } + + stateWhenNotNull = PossiblyConditionalState.Create(this); + Unsplit(); + Join(ref this.State, ref savedState); } - var accessTypeWithAnnotations = VisitLvalueWithAnnotations(node.AccessExpression); + var accessTypeWithAnnotations = LvalueResultType; TypeSymbol accessType = accessTypeWithAnnotations.Type; - Join(ref this.State, ref receiverState); - var oldType = node.Type; var resultType = oldType.IsVoidType() || oldType.IsErrorType() ? oldType : @@ -4302,6 +4425,22 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly SetResultType(node, TypeWithState.Create(resultType, NullableFlowState.MaybeDefault)); _currentConditionalReceiverVisitResult = default; _lastConditionalAccessSlot = previousConditionalAccessSlot; + + void makeAndAdjustReceiverSlot(BoundExpression receiver) + { + var slot = MakeSlot(receiver); + if (slot > 0 && receiver.Type?.IsNullableType() == true) + slot = GetNullableOfTValueSlot(receiver.Type, slot, out _); + + _lastConditionalAccessSlot = slot; + if (slot > -1) + LearnFromNonNullTest(slot, ref State); + } + } + + public override BoundNode? VisitConditionalAccess(BoundConditionalAccess node) + { + VisitConditionalAccess(node, out _); return null; } diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs index 591e04adcc780..30dde86b974e0 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs @@ -2385,6 +2385,63 @@ void M2(C c1) Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(17, 15)); } + [Fact] + public void NullCoalescing_CondAccess_NonNullConstantLeft() + { + var source = @" +#nullable enable + +static class C +{ + static string M0(this string s, object x) => s; + + static void M1() + { + object x, y; + _ = """"?.Equals(x = y = new object()) ?? false + ? x.ToString() + : y.ToString(); + } + + static void M2() + { + object w, x, y, z; + _ = """"?.M0(w = x = new object())?.Equals(y = z = new object()) ?? false + ? w.ToString() + y.ToString() + : x.ToString() + z.ToString(); // 1 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (21,30): error CS0165: Use of unassigned local variable 'z' + // : x.ToString() + z.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "z").WithArguments("z").WithLocation(21, 30) + ); + } + + [Fact] + public void NullCoalescing_NonNullConstantLeft() + { + var source = @" +#nullable enable + +static class C +{ + static void M1() + { + object x; + _ = """" ?? $""{x.ToString()}""; // unreachable + _ = """".ToString() ?? $""{x.ToString()}""; // 1 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (10,33): error CS0165: Use of unassigned local variable 'x' + // _ = "".ToString() ?? $"{x.ToString()}"; // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(10, 33) + ); + } + [Fact] public void NullCoalescing_ConditionalLeft() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index d2361595a7ab4..95eda8fc0fde7 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -46832,6 +46832,1410 @@ class CL1 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x1").WithLocation(11, 15)); } + [Fact] + public void NullCoalescing_RightSideBoolConstant() + { + var source = @" +class C +{ + bool M0(out object obj) { obj = new object(); return false; } + + static void M3(C? c, object? obj) + { + _ = c?.M0(out obj) ?? false + ? obj.ToString() + : obj.ToString(); // 1 + } + + static void M4(C? c, object? obj) + { + _ = c?.M0(out obj) ?? true + ? obj.ToString() // 2 + : obj.ToString(); + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(10, 15), + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(16, 15) + ); + } + + [Fact] + public void NullCoalescing_RightSideNullTest() + { + var source = @" +class C +{ + bool M0(out object obj) { obj = new object(); return false; } + + static void M1(C? c, object? obj) + { + _ = c?.M0(out obj) ?? obj != null + ? obj.ToString() + : obj.ToString(); // 1 + } + + static void M2(C? c, object? obj) + { + _ = c?.M0(out obj) ?? obj == null + ? obj.ToString() // 2 + : obj.ToString(); + } + + static void M5(C? c) + { + _ = c?.M0(out var obj) ?? obj != null // 3 + ? obj.ToString() + : obj.ToString(); // 4 + } + + static void M6(C? c) + { + _ = c?.M0(out var obj) ?? obj == null // 5 + ? obj.ToString() // 6 + : obj.ToString(); + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(10, 15), + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(16, 15), + // (22,35): error CS0165: Use of unassigned local variable 'obj' + // _ = c?.M0(out var obj) ?? obj != null // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(22, 35), + // (24,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(24, 15), + // (29,35): error CS0165: Use of unassigned local variable 'obj' + // _ = c?.M0(out var obj) ?? obj == null // 5 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(29, 35), + // (30,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(30, 15) + ); + } + + [Theory] + [InlineData("[NotNullWhen(true)] out object? obj")] + [InlineData("[MaybeNullWhen(false)] out object obj")] + public void NotNullWhenTrue_NullCoalescing_RightSideBool(string param) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class C +{ + bool M0(" + param + @") { obj = new object(); return true; } + + static void M1(C? c) + { + _ = c?.M0(out var obj) ?? false + ? obj.ToString() + : obj.ToString(); // 1, 2 + } + + static void M2(C? c) + { + _ = c?.M0(out var obj) ?? true + ? obj.ToString() // 3, 4 + : obj.ToString(); // 5 + } + + static void M3(C? c, bool b) + { + _ = c?.M0(out var obj1) ?? b + ? obj1.ToString() // 6, 7 + : """"; + + _ = c?.M0(out var obj2) ?? b + ? """" + : obj2.ToString(); // 8, 9 + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (12,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 1, 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(12, 15), + // (12,15): error CS0165: Use of unassigned local variable 'obj' + // : obj.ToString(); // 1, 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(12, 15), + // (18,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 3, 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(18, 15), + // (18,15): error CS0165: Use of unassigned local variable 'obj' + // ? obj.ToString() // 3, 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(18, 15), + // (19,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(19, 15), + // (25,15): warning CS8602: Dereference of a possibly null reference. + // ? obj1.ToString() // 6, 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj1").WithLocation(25, 15), + // (25,15): error CS0165: Use of unassigned local variable 'obj1' + // ? obj1.ToString() // 6, 7 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj1").WithArguments("obj1").WithLocation(25, 15), + // (30,15): warning CS8602: Dereference of a possibly null reference. + // : obj2.ToString(); // 8, 9 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj2").WithLocation(30, 15), + // (30,15): error CS0165: Use of unassigned local variable 'obj2' + // : obj2.ToString(); // 8, 9 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj2").WithArguments("obj2").WithLocation(30, 15) + ); + } + + [Theory] + [InlineData("[NotNullWhen(true)] out object? obj")] + [InlineData("[MaybeNullWhen(false)] out object obj")] + public void NotNullWhenTrue_NullCoalescing_RightSideNullTest(string param) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class C +{ + bool M0(" + param + @") { obj = new object(); return true; } + + static void M1(C? c, object? obj) + { + _ = c?.M0(out obj) ?? obj != null + ? obj.ToString() + : obj.ToString(); // 1 + } + + static void M2(C? c, object? obj) + { + _ = c?.M0(out obj) ?? obj == null + ? obj.ToString() // 2 + : obj.ToString(); // 3 + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (12,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(12, 15), + // (18,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(18, 15), + // (19,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(19, 15) + ); + } + + [Theory] + [InlineData("[NotNullWhen(false)] out object? obj")] + [InlineData("[MaybeNullWhen(true)] out object obj")] + public void NotNullWhenFalse_NullCoalescing_RightSideNullTest(string param) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class C +{ + bool M0(" + param + @") { obj = new object(); return true; } + + static void M1(C? c, object? obj) + { + _ = c?.M0(out obj) ?? obj != null + ? obj.ToString() // 1 + : obj.ToString(); // 2 + } + + static void M2(C? c, object? obj) + { + _ = c?.M0(out obj) ?? obj == null + ? obj.ToString() // 3 + : obj.ToString(); + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (11,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(11, 15), + // (12,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(12, 15), + // (18,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(18, 15) + ); + } + + [Theory] + [InlineData("[NotNullWhen(false)] out object? obj")] + [InlineData("[MaybeNullWhen(true)] out object obj")] + public void NotNullWhenFalse_NullCoalescing(string param) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class C +{ + bool M0(" + param + @") { obj = new object(); return false; } + + static void M1(C? c) + { + _ = c?.M0(out var obj) ?? false + ? obj.ToString() // 1 + : obj.ToString(); // 2, 3 + } + + static void M2(C? c) + { + _ = c?.M0(out var obj) ?? true + ? obj.ToString() // 4, 5 + : obj.ToString(); + } + + static void M3(C? c, bool b) + { + _ = c?.M0(out var obj1) ?? b + ? obj1.ToString() // 6, 7 + : """"; + + _ = c?.M0(out var obj2) ?? b + ? """" + : obj2.ToString(); // 8, 9 + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (11,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(11, 15), + // (12,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 2, 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(12, 15), + // (12,15): error CS0165: Use of unassigned local variable 'obj' + // : obj.ToString(); // 2, 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(12, 15), + // (18,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 4, 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(18, 15), + // (18,15): error CS0165: Use of unassigned local variable 'obj' + // ? obj.ToString() // 4, 5 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(18, 15), + // (25,15): warning CS8602: Dereference of a possibly null reference. + // ? obj1.ToString() // 6, 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj1").WithLocation(25, 15), + // (25,15): error CS0165: Use of unassigned local variable 'obj1' + // ? obj1.ToString() // 6, 7 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj1").WithArguments("obj1").WithLocation(25, 15), + // (30,15): warning CS8602: Dereference of a possibly null reference. + // : obj2.ToString(); // 8, 9 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj2").WithLocation(30, 15), + // (30,15): error CS0165: Use of unassigned local variable 'obj2' + // : obj2.ToString(); // 8, 9 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj2").WithArguments("obj2").WithLocation(30, 15) + ); + } + + [Fact] + [WorkItem(26624, "https://github.com/dotnet/roslyn/issues/26624")] + public void CondAccess_Multiple_Arguments() + { + var source = @" +static class C +{ + static bool? M0(this bool b, object? obj) { return b; } + + static void M1(bool? b, object? obj) + { + _ = b?.M0(obj = new object()) ?? false + ? obj.ToString() + : obj.ToString(); // 1 + } + + static void M2(bool? b) + { + var obj = new object(); + _ = b?.M0(obj = null)?.M0(obj = new object()) ?? false + ? obj.ToString() + : obj.ToString(); // 2 + } + + static void M3(bool? b) + { + var obj = new object(); + _ = b?.M0(obj = new object())?.M0(obj = null) ?? false + ? obj.ToString() // 3 + : obj.ToString(); // 4 + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(10, 15), + // (18,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(18, 15), + // (25,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(25, 15), + // (26,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(26, 15) + ); + } + + [Fact] + [WorkItem(26624, "https://github.com/dotnet/roslyn/issues/26624")] + public void NullCoalescing_LeftStateAfterExpression() + { + var source = @" +class C +{ + static void M1(bool? flag) + { + _ = flag ?? false + ? flag.Value + : flag.Value; // 1 + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (8,15): warning CS8629: Nullable value type may be null. + // : flag.Value; // 1 + Diagnostic(ErrorCode.WRN_NullableValueTypeMayBeNull, "flag").WithLocation(8, 15) + ); + } + + ///Ported from . + [Fact] + public void CondAccess_NullCoalescing_01() + { + var source = @" +class C +{ + void M1(C c, object? x) + { + _ = c?.M(x = new object()) ?? true + ? x.ToString() // 1 + : x.ToString(); + } + + void M2(C c, object? x) + { + _ = c?.M(x = new object()) ?? false + ? x.ToString() + : x.ToString(); // 2; + } + + bool M(object x) => true; +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (7,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(7, 15), + // (15,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2; + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 15) + ); + } + + ///Ported from . + [Fact] + public void CondAccess_NullCoalescing_02() + { + var source = @" +class C +{ + void M1(C c1, C c2, object? x) + { + _ = c1?.M(x = new object()) ?? c2?.M(x = new object()) ?? true + ? x.ToString() // 1 + : x.ToString(); + } + + void M2(C c1, C c2, object? x) + { + _ = c1?.M(x = new object()) ?? c2?.M(x = new object()) ?? false + ? x.ToString() + : x.ToString(); // 2; + } + + bool M(object x) => true; +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (7,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(7, 15), + // (15,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2; + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 15) + ); + } + + ///Ported from . + [Fact] + public void CondAccess_NullCoalescing_03() + { + var source = @" +class C +{ + void M1(C c1, object? x) + { + _ = c1?.MA().MB(x = new object()) ?? false + ? x.ToString() + : x.ToString(); // 1 + } + + void M2(C c1, object? x) + { + _ = c1?.MA()?.MB(x = new object()) ?? false + ? x.ToString() + : x.ToString(); // 2; + } + + C MA() => this; + bool MB(object x) => true; +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (8,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 15), + // (15,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2; + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 15) + ); + } + + ///Ported from . + [Fact] + public void CondAccess_NullCoalescing_04() + { + var source = @" +class C +{ + void M1(C c1, object? x) + { + _ = c1?.MA(x = new object()).MB() ?? false + ? x.ToString() + : x.ToString(); // 1 + } + + void M2(C c1, object? x) + { + _ = c1?.MA(x = new object())?.MB() ?? false + ? x.ToString() + : x.ToString(); // 2; + } + + C MA(object x) => this; + bool MB() => true; +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (8,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 15), + // (15,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2; + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 15) + ); + } + + ///Ported from . + [Fact] + public void CondAccess_NullCoalescing_05() + { + var source = @" +class C +{ + void M1(C c1, object? x) + { + _ = c1?.MA(c1.MB(x = new object())) ?? false + ? x.ToString() + : x.ToString(); // 1 + } + + void M2(C c1, object? x) + { + _ = c1?.MA(c1?.MB(x = new object())) ?? false + ? x.ToString() // 2 + : x.ToString(); // 3 + } + + bool MA(object? obj) => true; + C MB(object x) => this; +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (8,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 15), + // (14,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(14, 15), + // (15,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 15) + ); + } + + ///Ported from . + [Fact] + public void CondAccess_NullCoalescing_06() + { + var source = @" +class C +{ + void M1(C c1, object? x) + { + _ = c1?.M(out x) ?? false + ? x.ToString() + : x.ToString(); // 1 + } + + void M2(C c1, object? x) + { + _ = c1?.M(out x) ?? false + ? x.ToString() + : x.ToString(); // 2 + } + + bool M(out object obj) { obj = new object(); return true; } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (8,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 15), + // (15,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 15) + ); + } + + ///Ported from . + [Fact] + public void CondAccess_NullCoalescing_07() + { + var source = @" +class C +{ + void M1(bool b, C c1, object? x) + { + _ = c1?.M(x = new object()) ?? b + ? x.ToString() // 1 + : x.ToString(); // 2 + } + + bool M(object obj) { return true; } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (7,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(7, 15), + // (8,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 15) + ); + } + + ///Ported from . + [Fact] + public void CondAccess_NullCoalescing_08() + { + var source = @" +class C +{ + void M1(C c1, object? x) + { + _ = (c1?.M(x = 0)) ?? false + ? x.ToString() + : x.ToString(); // 1 + } + + void M2(C c1, object? x) + { + _ = c1?.M(x = 0)! ?? false + ? x.ToString() + : x.ToString(); // 2 + } + + void M3(C c1, object? x) + { + _ = (c1?.M(x = 0))! ?? false + ? x.ToString() + : x.ToString(); // 3 + } + + bool M(object obj) { return true; } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (8,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 15), + // (15,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 15), + // (22,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(22, 15) + ); + } + + ///Ported from . + [Fact] + public void CondAccess_NullCoalescing_09() + { + var source = @" +class C +{ + + void M1(C c1, object? x) + { + _ = (c1?[x = 0]) ?? false + ? x.ToString() + : x.ToString(); // 1 + } + + void M2(C c1, object? x) + { + _ = (c1?[x = 0]) ?? true + ? x.ToString() // 2 + : x.ToString(); + } + + public bool this[object x] => false; +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (9,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(9, 15), + // (15,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 15) + ); + } + + ///Ported from . + [Fact] + public void CondAccess_NullCoalescing_10() + { + var source = @" +class C +{ + + void M1(C c1, object? x) + { + _ = (bool?)null ?? (c1?.M(x = 0) ?? false) + ? x.ToString() + : x.ToString(); // 1 + } + + void M2(C c1, object? x) + { + _ = (bool?)null ?? (c1?.M(x = 0) ?? true) + ? x.ToString() // 2 + : x.ToString(); + } + + bool M(object obj) { return true; } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (9,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(9, 15), + // (15,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 15)); + } + + ///Ported from . + [Fact] + public void NullCoalescing_ConditionalLeft() + { + var source = @" +class C +{ + void M1(C c1, bool b, object? x) + { + _ = (bool?)(b && c1.M(x = 0)) ?? false + ? x.ToString() // 1 + : x.ToString(); // 2 + } + + void M2(C c1, bool b, object? x) + { + _ = (bool?)c1.M(x = 0) ?? false + ? x.ToString() + : x.ToString(); + } + + void M3(C c1, bool b, object? x, object? y) + { + _ = (bool?)((y = 0) is 0 && c1.M(x = 0)) ?? false + ? x.ToString() + y.ToString() // 3 + : x.ToString() + y.ToString(); // 4 + } + + bool M(object obj) { return true; } +} +"; + // Note that we unsplit any conditional state after visiting the left side of `??`. + CreateNullableCompilation(source).VerifyDiagnostics( + // (7,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(7, 15), + // (8,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 15), + // (21,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() + y.ToString() // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(21, 15), + // (22,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString() + y.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(22, 15) + ); + } + + ///Ported from . + [Fact] + public void NullCoalescing_CondAccess_Throw() + { + var source = @" +class C +{ + void M1(C c1, bool b, object? x) + { + _ = c1?.M(x = 0) ?? throw new System.Exception(); + x.ToString(); + } + + bool M(object obj) { return true; } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics(); + } + + ///Ported from . + [Fact] + public void NullCoalescing_CondAccess_Cast() + { + var source = @" +class C +{ + void M1(C c1, bool b, object? x) + { + _ = (object)c1?.M(x = 0) ?? throw new System.Exception(); // 1 + x.ToString(); + } + + C M(object obj) { return this; } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (6,13): warning CS8600: Converting null literal or possible null value to non-nullable type. + // _ = (object)c1?.M(x = 0) ?? throw new System.Exception(); // 1 + Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "(object)c1?.M(x = 0)").WithLocation(6, 13)); + } + + ///Ported from . + [Fact] + public void NullCoalescing_CondAccess_UserDefinedConv_01() + { + var source = @" +struct S { } + +struct C +{ + public static implicit operator S(C? c) + { + return default(S); + } + + void M1(C? c1, object? x) + { + S s = c1?.M2(x = 0) ?? c1!.Value.M3(x = 0); + x.ToString(); + } + + C M2(object obj) { return this; } + S M3(object obj) { return this; } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics(); + } + + ///Ported from . + [Fact] + public void NullCoalescing_CondAccess_UserDefinedConv_02() + { + var source = @" +class B { } +class C +{ + public static implicit operator B(C c) => new B(); + + void M1(C c1, object? x) + { + B b = c1?.M1(x = 0) ?? c1!.M2(x = 0); + x.ToString(); + } + + C M1(object obj) { return this; } + B M2(object obj) { return new B(); } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics(); + } + + ///Ported from . + [Fact] + public void NullCoalescing_CondAccess_UserDefinedConv_03() + { + var source = @" +struct B { } +struct C +{ + public static implicit operator B(C c) => new B(); + + void M1(C? c1, object? x) + { + B b = c1?.M1(x = 0) ?? c1!.Value.M2(x = 0); + x.ToString(); + } + + C M1(object obj) { return this; } + B M2(object obj) { return new B(); } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics(); + } + + ///Ported from . + [Fact] + public void NullCoalescing_CondAccess_UserDefinedConv_04() + { + var source = @" +struct B { } +struct C +{ + public static implicit operator B?(C c) => null; + + void M1(C? c1, object? x) + { + B? b = c1?.M1(x = 0) ?? c1!.Value.M2(x = 0); + x.ToString(); + } + + C M1(object obj) { return this; } + B? M2(object obj) { return new B(); } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics(); + } + + ///Ported from . + [Fact] + public void NullCoalescing_CondAccess_UserDefinedConv_05() + { + var source = @" +struct B +{ + public static implicit operator B(C c) => default; +} + +class C +{ + static void M1(C c1, object? x) + { + B b = c1?.M1(x = 0) ?? c1!.M2(x = 0); + x.ToString(); + } + + C M1(object obj) { return this; } + B M2(object obj) { return this; } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics(); + } + + ///Ported from . + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_01_01(string conversionKind) + { + var source = @" +struct S { } + +struct C +{ + public static " + conversionKind + @" operator S(C? c) + { + return default(S); + } + + void M1(C? c1, object? x) + { + S s = (S?)c1?.M2(x = 0) ?? c1!.Value.M3(x = 0); + x.ToString(); // 1 + } + + C M2(object obj) { return this; } + S M3(object obj) { return (S)this; } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (14,9): warning CS8602: Dereference of a possibly null reference. + // x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(14, 9) + ); + } + + ///Ported from . + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_01_02(string conversionKind) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +struct S { } + +struct C +{ + public static " + conversionKind + @" operator S([DisallowNull] C? c) + { + return default(S); + } + + void M1(C? c1, object? x) + { + S s = (S?)c1?.M2(x = 0) ?? c1!.Value.M3(x = 0); // 1 + x.ToString(); // 2 + } + + C M2(object obj) { return this; } + S M3(object obj) { return (S)this; } +} +"; + CreateNullableCompilation(new[] { source, DisallowNullAttributeDefinition }).VerifyDiagnostics( + // (15,19): warning CS8607: A possible null value may not be used for a type marked with [NotNull] or [DisallowNull] + // S s = (S?)c1?.M2(x = 0) ?? c1!.Value.M3(x = 0); // 1 + Diagnostic(ErrorCode.WRN_DisallowNullAttributeForbidsMaybeNullAssignment, "c1?.M2(x = 0)").WithLocation(15, 19), + // (16,9): warning CS8602: Dereference of a possibly null reference. + // x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(16, 9) + ); + } + + ///Ported from . + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_02_01(string conversionKind) + { + var source = @" +class B { } +class C +{ + public static " + conversionKind + @" operator B(C? c) => new B(); + + void M1(C c1, object? x) + { + B b = (B)c1?.M1(x = 0) ?? c1!.M2(x = 0); + x.ToString(); // 1 + } + + C M1(object obj) { return this; } + B M2(object obj) { return new B(); } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (10,9): warning CS8602: Dereference of a possibly null reference. + // x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 9)); + } + + ///Ported from . + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_02_02(string conversionKind) + { + var source = @" +class B { } +class C +{ + public static " + conversionKind + @" operator B?(C? c) => new B(); + + void M1(C c1, object? x) + { + B b = (B?)c1?.M1(x = 0) ?? c1!.M2(x = 0); + x.ToString(); // 1 + } + + C M1(object obj) { return this; } + B M2(object obj) { return new B(); } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (10,9): warning CS8602: Dereference of a possibly null reference. + // x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 9)); + } + + ///Ported from . + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_02_03(string conversionKind) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class B { } +class C +{ + [return: NotNullIfNotNull(""c"")] + public static " + conversionKind + @" operator B?(C? c) => new B(); + + void M1(C c1, object? x) + { + B b = (B?)c1?.M1(x = 0) ?? c1!.M2(x = 0); + x.ToString(); // 1 + } + + C M1(object obj) { return this; } + B M2(object obj) { return new B(); } +} +"; + CreateNullableCompilation(new[] { source, NotNullIfNotNullAttributeDefinition }).VerifyDiagnostics( + // (13,9): warning CS8602: Dereference of a possibly null reference. + // x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(13, 9)); + } + + ///Ported from . + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_02_04(string conversionKind) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class B { } +class C +{ + [return: NotNull] + public static " + conversionKind + @" operator B?(C? c) => new B(); + + void M1(C c1, object? x) + { + B b = (B?)c1?.M1(x = 0) ?? c1!.M2(x = 0); + x.ToString(); // 1 + } + + C M1(object obj) { return this; } + B M2(object obj) { return new B(); } +} +"; + CreateNullableCompilation(new[] { source, NotNullAttributeDefinition }).VerifyDiagnostics( + // (13,9): warning CS8602: Dereference of a possibly null reference. + // x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(13, 9)); + } + + ///Ported from . + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_02_05(string conversionKind) + { + var source = @" +class B { } +class C +{ + public static " + conversionKind + @" operator B(C c) => new B(); + + void M1(C c1, object? x) + { + B b = (B?)c1?.M1(x = 0) ?? c1!.M2(x = 0); // 1 + x.ToString(); // 2 + } + + C M1(object obj) { return this; } + B M2(object obj) { return new B(); } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (9,19): warning CS8604: Possible null reference argument for parameter 'c' in 'C.implicit operator B(C c)'. + // B b = (B?)c1?.M1(x = 0) ?? c1!.M2(x = 0); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "c1?.M1(x = 0)").WithArguments("c", "C." + conversionKind + " operator B(C c)").WithLocation(9, 19), + // (10,9): warning CS8602: Dereference of a possibly null reference. + // x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 9) + ); + } + + ///Ported from . + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_03(string conversionKind) + { + var source = @" +struct B { } +struct C +{ + public static " + conversionKind + @" operator B(C c) => new B(); + + void M1(C? c1, object? x) + { + B b = (B?)c1?.M1(x = 0) ?? c1!.Value.M2(x = 0); + x.ToString(); + } + + C M1(object obj) { return this; } + B M2(object obj) { return new B(); } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics(); + } + + ///Ported from . + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_04(string conversionKind) + { + var source = @" +struct B { } +struct C +{ + public static " + conversionKind + @" operator B?(C c) => null; + + void M1(C? c1, object? x) + { + B? b = (B?)c1?.M1(x = 0) ?? c1!.Value.M2(x = 0); + x.ToString(); + } + + C M1(object obj) { return this; } + B? M2(object obj) { return new B(); } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics(); + } + + ///Ported from . + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_05_01(string conversionKind) + { + var source = @" +struct B +{ + public static " + conversionKind + @" operator B(C? c) => default; +} + +class C +{ + static void M1(C c1, object? x) + { + B b = (B?)c1?.M1(x = 0) ?? c1!.M2(x = 0); + x.ToString(); // 1 + } + + C M1(object obj) { return this; } + B M2(object obj) { return default; } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (12,9): warning CS8602: Dereference of a possibly null reference. + // x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(12, 9) + ); + } + + ///Ported from . + [Theory] + [InlineData("explicit")] + [InlineData("implicit")] + public void NullCoalescing_CondAccess_ExplicitUserDefinedConv_05_02(string conversionKind) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +struct B +{ + public static " + conversionKind + @" operator B([DisallowNull] C? c) => default; +} + +class C +{ + static void M1(C c1, object? x) + { + B b = (B?)c1?.M1(x = 0) ?? c1!.M2(x = 0); // 1 + x.ToString(); // 2 + } + + C M1(object obj) { return this; } + B M2(object obj) { return default; } +} +"; + CreateNullableCompilation(new[] { source, DisallowNullAttributeDefinition }).VerifyDiagnostics( + // (13,19): warning CS8604: Possible null reference argument for parameter 'c' in 'B.implicit operator B(C? c)'. + // B b = (B?)c1?.M1(x = 0) ?? c1!.M2(x = 0); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "c1?.M1(x = 0)").WithArguments("c", "B." + conversionKind + " operator B(C? c)").WithLocation(13, 19), + // (14,9): warning CS8602: Dereference of a possibly null reference. + // x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(14, 9) + ); + } + + ///Ported from . + [Fact] + public void NullCoalescing_CondAccess_NullableEnum() + { + var source = @" +public enum E { E1 = 1 } + +public static class Extensions +{ + public static E M1(this E e, object obj) => e; + + static void M2(E? e, object? x) + { + E e2 = e?.M1(x = 0) ?? e!.Value.M1(x = 0); + x.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics(); + } + + /// Ported from . + [Fact] + public void NullCoalescing_CondAccess_NonNullConstantLeft() + { + var source = @" +static class C +{ + static string M0(this string s, object? x) => s; + + static void M1(object? x) + { + _ = """"?.Equals(x = new object()) ?? false + ? x.ToString() + : x.ToString(); + } + + static void M2(object? x, object? y) + { + _ = """"?.M0(x = new object())?.Equals(y = new object()) ?? false + ? x.ToString() + y.ToString() + : x.ToString() + y.ToString(); // 1 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (17,30): warning CS8602: Dereference of a possibly null reference. + // : x.ToString() + y.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(17, 30)); + } + + /// Ported from . + [Fact] + public void NullCoalescing_NonNullConstantLeft() + { + var source = @" +static class C +{ + static void M1(object? x) + { + _ = """" ?? $""{x.ToString()}""; // unreachable + _ = """".ToString() ?? $""{x.ToString()}""; // 1 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (7,33): warning CS8602: Dereference of a possibly null reference. + // _ = "".ToString() ?? $"{x.ToString()}"; // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(7, 33) + ); + } + + [Fact] + public void NullCoalescing_CondAccess_NullableClassConstraint() + { + var source = @" +interface I +{ + bool M0(object obj); +} + +static class C where T : class?, I +{ + static void M1(T t, object? x) + { + _ = t?.M0(x = 1) ?? false + ? t.ToString() + x.ToString() + : t.ToString() + x.ToString(); // 1, 2 + } + + static void M2(T t, object? x) + { + _ = t?.M0(x = 1) ?? false + ? M2(t) + x.ToString() + : M2(t) + x.ToString(); // 3, 4 + } + + // 'T' is allowed as an argument with a maybe-null state, but not with a maybe-default state. + static string M2(T t) => t!.ToString(); +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (13,15): warning CS8602: Dereference of a possibly null reference. + // : t.ToString() + x.ToString(); // 1, 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "t").WithLocation(13, 15), + // (13,30): warning CS8602: Dereference of a possibly null reference. + // : t.ToString() + x.ToString(); // 1, 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(13, 30), + // (20,18): warning CS8604: Possible null reference argument for parameter 't' in 'string C.M2(T t)'. + // : M2(t) + x.ToString(); // 3, 4 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "t").WithArguments("t", "string C.M2(T t)").WithLocation(20, 18), + // (20,23): warning CS8602: Dereference of a possibly null reference. + // : M2(t) + x.ToString(); // 3, 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(20, 23) + ); + } + [Fact] public void ConditionalBranching_Is_ReferenceType() { From 182dd830ad84c008343710076d8a582991820f27 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 10 May 2021 16:50:28 -0700 Subject: [PATCH 6/9] Improved nullable conditional operator analysis (#52783) --- .../Portable/FlowAnalysis/NullableWalker.cs | 102 +++-- .../Semantics/NullableReferenceTypesTests.cs | 423 +++++++++++++++++- 2 files changed, 461 insertions(+), 64 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 4a41d0c627b90..18fb9af97167d 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4184,21 +4184,7 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly Visit(rightOperand); TypeWithState rightResult = ResultType; - if (whenNotNull.IsConditionalState) - { - Split(); - } - - if (IsConditionalState) - { - Join(ref StateWhenTrue, ref whenNotNull.IsConditionalState ? ref whenNotNull.StateWhenTrue : ref whenNotNull.State); - Join(ref StateWhenFalse, ref whenNotNull.IsConditionalState ? ref whenNotNull.StateWhenFalse : ref whenNotNull.State); - } - else - { - Debug.Assert(!whenNotNull.IsConditionalState); - Join(ref State, ref whenNotNull.State); - } + Join(ref whenNotNull); var leftResultType = leftResult.Type; var rightResultType = rightResult.Type; @@ -4486,40 +4472,18 @@ void makeAndAdjustReceiverSlot(BoundExpression receiver) return null; } - BoundExpression consequence; - BoundExpression alternative; - Conversion consequenceConversion; - Conversion alternativeConversion; - bool consequenceEndReachable; - bool alternativeEndReachable; + (var consequence, var consequenceConversion, consequenceRValue) = visitConditionalOperand(consequenceState, originalConsequence); + var consequenceConditionalState = PossiblyConditionalState.Create(this); + consequenceState = CloneAndUnsplit(ref consequenceConditionalState); + var consequenceEndReachable = consequenceState.Reachable; - // In cases where one branch is unreachable, we don't need to Unsplit the state - if (!alternativeState.Reachable) - { - (alternative, alternativeConversion, alternativeRValue) = visitConditionalOperand(alternativeState, originalAlternative); - (consequence, consequenceConversion, consequenceRValue) = visitConditionalOperand(consequenceState, originalConsequence); - alternativeEndReachable = false; - consequenceEndReachable = IsReachable(); - } - else if (!consequenceState.Reachable) - { - (consequence, consequenceConversion, consequenceRValue) = visitConditionalOperand(consequenceState, originalConsequence); - (alternative, alternativeConversion, alternativeRValue) = visitConditionalOperand(alternativeState, originalAlternative); - consequenceEndReachable = false; - alternativeEndReachable = IsReachable(); - } - else - { - (consequence, consequenceConversion, consequenceRValue) = visitConditionalOperand(consequenceState, originalConsequence); - Unsplit(); - consequenceState = this.State; - consequenceEndReachable = consequenceState.Reachable; + (var alternative, var alternativeConversion, alternativeRValue) = visitConditionalOperand(alternativeState, originalAlternative); + var alternativeConditionalState = PossiblyConditionalState.Create(this); + alternativeState = CloneAndUnsplit(ref alternativeConditionalState); + var alternativeEndReachable = alternativeState.Reachable; - (alternative, alternativeConversion, alternativeRValue) = visitConditionalOperand(alternativeState, originalAlternative); - Unsplit(); - alternativeEndReachable = this.State.Reachable; - Join(ref this.State, ref consequenceState); - } + SetPossiblyConditionalState(in consequenceConditionalState); + Join(ref alternativeConditionalState); TypeSymbol? resultType; bool wasTargetTyped = node is BoundConditionalOperator { WasTargetTyped: true }; @@ -9844,6 +9808,50 @@ protected override bool Join(ref LocalState self, ref LocalState other) return self.Join(in other); } + private void Join(ref PossiblyConditionalState other) + { + var otherIsConditional = other.IsConditionalState; + if (otherIsConditional) + { + Split(); + } + + if (IsConditionalState) + { + Join(ref StateWhenTrue, ref otherIsConditional ? ref other.StateWhenTrue : ref other.State); + Join(ref StateWhenFalse, ref otherIsConditional ? ref other.StateWhenFalse : ref other.State); + } + else + { + Debug.Assert(!otherIsConditional); + Join(ref State, ref other.State); + } + } + + private LocalState CloneAndUnsplit(ref PossiblyConditionalState conditionalState) + { + if (!conditionalState.IsConditionalState) + { + return conditionalState.State.Clone(); + } + + var state = conditionalState.StateWhenTrue.Clone(); + Join(ref state, ref conditionalState.StateWhenFalse); + return state; + } + + private void SetPossiblyConditionalState(in PossiblyConditionalState conditionalState) + { + if (!conditionalState.IsConditionalState) + { + SetState(conditionalState.State); + } + else + { + SetConditionalState(conditionalState.StateWhenTrue, conditionalState.StateWhenFalse); + } + } + internal sealed class LocalStateSnapshot { internal readonly int Id; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 71e1c11f05475..3b11ab11c0140 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -46832,6 +46832,400 @@ class CL1 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x1").WithLocation(11, 15)); } + /// Ported from . + [Theory] + [InlineData("true", "false")] + [InlineData("FIELD_TRUE", "FIELD_FALSE")] + [InlineData("LOCAL_TRUE", "LOCAL_FALSE")] + [InlineData("true || false", "true && false")] + [InlineData("!false", "!true")] + public void ConditionalConstant_01(string @true, string @false) + { + var source = @" +#pragma warning disable 219 // The variable is assigned but its value is never used + +class C +{ + const bool FIELD_TRUE = true; + const bool FIELD_FALSE = false; + + static void M(bool b, object? x) + { + const bool LOCAL_TRUE = true; + const bool LOCAL_FALSE = false; + + x = null; + _ = (b ? x != null : " + @false + @") // `b && x != null` + ? x.ToString() + : x.ToString(); // 1 + + x = new object(); + _ = (b ? x != null : " + @false + @") // `b && x != null` + ? x.ToString() + : x.ToString(); // 2 + + + x = null; + _ = (b ? x != null : " + @true + @") // `!b || x != null` + ? x.ToString() // 3 + : x.ToString(); // 4 + + x = new object(); + _ = (b ? x != null : " + @true + @") // `!b || x != null` + ? x.ToString() + : x.ToString(); // 5 + + + x = null; + _ = (b ? " + @false + @" : x != null) // `!b && x != null` + ? x.ToString() + : x.ToString(); // 6 + + x = new object(); + _ = (b ? " + @false + @" : x != null) // `!b && x != null` + ? x.ToString() + : x.ToString(); // 7 + + + x = null; + _ = (b ? " + @true + @" : x != null) // `b || x != null` + ? x.ToString() // 8 + : x.ToString(); // 9 + + x = new object(); + _ = (b ? " + @true + @" : x != null) // `b || x != null` + ? x.ToString() + : x.ToString(); // 10 + + + x = null; + _ = !(b ? " + @true + @" : x != null) // `!(b || x != null)` -> `!b && x == null` + ? x.ToString() // 11 + : x.ToString(); // 12 + + x = new object(); + _ = !(b ? " + @true + @" : x != null) // `!(b || x != null)` -> `!b && x == null` + ? x.ToString() // 13 + : x.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (17,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(17, 15), + // (22,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(22, 15), + // (27,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(27, 15), + // (28,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(28, 15), + // (33,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(33, 15), + // (39,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(39, 15), + // (44,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(44, 15), + // (49,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 8 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(49, 15), + // (50,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 9 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(50, 15), + // (55,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 10 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(55, 15), + // (60,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 11 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(60, 15), + // (61,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 12 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(61, 15), + // (65,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 13 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(65, 15) + ); + } + + /// Ported from . + [Theory] + [InlineData("true", "false")] + [InlineData("FIELD_TRUE", "FIELD_FALSE")] + [InlineData("LOCAL_TRUE", "LOCAL_FALSE")] + [InlineData("true || false", "true && false")] + [InlineData("!false", "!true")] + public void ConditionalConstant_02(string @true, string @false) + { + var source = @" +#pragma warning disable 219 // The variable is assigned but its value is never used + +class C +{ + const bool FIELD_TRUE = true; + const bool FIELD_FALSE = false; + + static void M(bool b, object? x) + { + const bool LOCAL_TRUE = true; + const bool LOCAL_FALSE = false; + + x = null; + _ = (b ? x == null : " + @false + @") // `b && x == null` + ? x.ToString() // 1 + : x.ToString(); // 2 + + x = new object(); + _ = (b ? x == null : " + @false + @") // `b && x == null` + ? x.ToString() // 3 + : x.ToString(); + + + x = null; + _ = (b ? x == null : " + @true + @") // `!b || x == null` + ? x.ToString() // 4 + : x.ToString(); + + x = new object(); + _ = (b ? x == null : " + @true + @") // `!b || x == null` + ? x.ToString() // 5 + : x.ToString(); + + + x = null; + _ = (b ? " + @false + @" : x == null) // `!b && x == null` + ? x.ToString() // 6 + : x.ToString(); // 7 + + x = new object(); + _ = (b ? " + @false + @" : x == null) // `!b && x == null` + ? x.ToString() // 8 + : x.ToString(); + + + x = null; + _ = !(b ? " + @false + @" : x == null) // `!(!b && x == null)` -> `b || x != null` + ? x.ToString() // 9 + : x.ToString(); // 10 + + x = new object(); + _ = !(b ? " + @false + @" : x == null) // `!(!b && x == null)` -> `b || x != null` + ? x.ToString() + : x.ToString(); // 11 + + + x = null; + _ = (b ? " + @true + @" : x == null) // `b || x == null` + ? x.ToString() // 12 + : x.ToString(); + + x = new object(); + _ = (b ? " + @true + @" : x == null) // `b || x == null` + ? x.ToString() // 13 + : x.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(16, 15), + // (17,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(17, 15), + // (21,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(21, 15), + // (27,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(27, 15), + // (32,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(32, 15), + // (38,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(38, 15), + // (39,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(39, 15), + // (43,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 8 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(43, 15), + // (49,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 9 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(49, 15), + // (50,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 10 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(50, 15), + // (55,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 11 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(55, 15), + // (60,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 12 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(60, 15), + // (65,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 13 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(65, 15) + ); + } + + /// Ported from + [Fact] + public void IfConditional_ComplexCondition_ConstantConsequence() + { + var source = @" +class C +{ + bool M0(int x) => true; + + void M1(object? x) + { + _ = (x != null ? true : false) + ? x.ToString() + : x.ToString(); // 1 + } + + void M2(object? x) + { + _ = (x != null ? false : true) + ? x.ToString() // 2 + : x.ToString(); + } + + void M3(object? x) + { + _ = (x == null ? true : false) + ? x.ToString() // 3 + : x.ToString(); + } + + void M4(object? x) + { + _ = (x == null ? false : true) + ? x.ToString() + : x.ToString(); // 4 + } + + void M5(object? x) + { + _ = (!(x == null ? false : true)) + ? x.ToString() // 5 + : x.ToString(); + } +} +"; + var comp = CreateNullableCompilation(source); + comp.VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 15), + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(16, 15), + // (23,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(23, 15), + // (31,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(31, 15), + // (37,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(37, 15) + ); + } + + [Fact] + public void ConditionalOperator_OperandConditionalState() + { + var source = @" +class C +{ + void M1(object? x, bool b) + { + _ = (b ? x != null : x != null) + ? x.ToString() + : x.ToString(); // 1 + } + + void M2(object? x, bool b) + { + _ = (b ? x != null : x == null) + ? x.ToString() // 2 + : x.ToString(); // 3 + } + + void M3(object? x, bool b) + { + _ = (b ? x == null : x != null) + ? x.ToString() // 4 + : x.ToString(); // 5 + } + + void M4(object? x, bool b) + { + _ = (b ? x == null : x == null) + ? x.ToString() // 6 + : x.ToString(); + } +}"; + var comp = CreateNullableCompilation(source); + comp.VerifyDiagnostics( + // (8,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 15), + // (14,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(14, 15), + // (15,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 15), + // (21,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(21, 15), + // (22,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(22, 15), + // (28,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(28, 15)); + } + + [Fact] + public void ConditionalOperator_OperandUnreachable() + { + var source = @" +class C +{ + void M1(object? x, bool b) + { + _ = (b ? x != null : throw null!) + ? x.ToString() + : x.ToString(); // 1 + } + + void M2(object? x, bool b) + { + _ = (b ? throw null! : x == null) + ? x.ToString() // 2 + : x.ToString(); + } +}"; + var comp = CreateNullableCompilation(source); + comp.VerifyDiagnostics( + // (8,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 15), + // (14,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(14, 15) + ); + } + [Fact] public void NullCoalescing_RightSideBoolConstant() { @@ -129511,31 +129905,26 @@ static void Main(string? x, string? y) if (x?.Length == 2 ? y?.Length == 2 : y?.Length == 3) { Console.WriteLine(x.Length); // 1 - Console.WriteLine(y.Length); // 2 + Console.WriteLine(y.Length); } else { - Console.WriteLine(x.Length); // 3 - Console.WriteLine(y.Length); // 4 + Console.WriteLine(x.Length); // 2 + Console.WriteLine(y.Length); // 3 } } }"; - // The first warning produced for y.Length is unexpected. - // https://github.com/dotnet/roslyn/issues/36096 var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (10,31): warning CS8602: Dereference of a possibly null reference. - // Console.WriteLine(x.Length); // 1 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 31), - // (11,31): warning CS8602: Dereference of a possibly null reference. - // Console.WriteLine(y.Length); // 2 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(11, 31), - // (15,31): warning CS8602: Dereference of a possibly null reference. - // Console.WriteLine(x.Length); // 3 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 31), - // (16,31): warning CS8602: Dereference of a possibly null reference. - // Console.WriteLine(y.Length); // 4 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(16, 31) + // (10,31): warning CS8602: Dereference of a possibly null reference. + // Console.WriteLine(x.Length); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 31), + // (15,31): warning CS8602: Dereference of a possibly null reference. + // Console.WriteLine(x.Length); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 31), + // (16,31): warning CS8602: Dereference of a possibly null reference. + // Console.WriteLine(y.Length); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(16, 31) ); } From d5eed346d35c27771fced2c36d00b4ba31d5067b Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Thu, 20 May 2021 17:01:38 -0700 Subject: [PATCH 7/9] Improved nullable '=='/'!=' analysis (#53198) --- .../Portable/FlowAnalysis/AbstractFlowPass.cs | 42 +- .../Portable/FlowAnalysis/NullableWalker.cs | 271 ++- .../Test/Semantic/FlowAnalysis/FlowTests.cs | 197 ++ .../Semantics/NullableReferenceTypesTests.cs | 1643 ++++++++++++++++- .../Semantics/OverloadResolutionPerfTests.cs | 39 + .../Symbols/Source/NullablePublicAPITests.cs | 91 + 6 files changed, 2180 insertions(+), 103 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index 37f38869ce494..b1c62589638b5 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -2697,7 +2697,7 @@ private bool TryVisitConditionalAccess(BoundExpression node, [NotNullWhen(true)] var access = node switch { BoundConditionalAccess ca => ca, - BoundConversion { Conversion: Conversion innerConversion, Operand: BoundConditionalAccess ca } when CanPropagateStateWhenNotNull(ca, innerConversion) => ca, + BoundConversion { Conversion: Conversion conversion, Operand: BoundConditionalAccess ca } when CanPropagateStateWhenNotNull(conversion) => ca, _ => null }; @@ -2713,30 +2713,29 @@ private bool TryVisitConditionalAccess(BoundExpression node, [NotNullWhen(true)] stateWhenNotNull = default; return false; - } /// /// "State when not null" can only propagate out of a conditional access if /// it is not subject to a user-defined conversion whose parameter is not of a non-nullable value type. /// - protected static bool CanPropagateStateWhenNotNull(BoundConditionalAccess operand, Conversion conversion) + protected static bool CanPropagateStateWhenNotNull(Conversion conversion) { - if (conversion.Kind is not (ConversionKind.ImplicitUserDefined or ConversionKind.ExplicitUserDefined)) + if (!conversion.IsValid) { - return true; + return false; } - var method = conversion.Method; - Debug.Assert(method is not null); - Debug.Assert(method.ParameterCount is 1); - var param = method.Parameters[0]; - if (operand.Type.IsNullableType() && param.Type.IsNonNullableValueType()) + if (!conversion.IsUserDefined) { return true; } - return false; + var method = conversion.Method; + Debug.Assert(method is object); + Debug.Assert(method.ParameterCount is 1); + var param = method.Parameters[0]; + return param.Type.IsNonNullableValueType(); } /// @@ -2759,7 +2758,17 @@ private bool VisitPossibleConditionalAccess(BoundExpression node, [NotNullWhen(t private void VisitConditionalAccess(BoundConditionalAccess node, out TLocalState stateWhenNotNull) { - VisitRvalue(node.Receiver); + // The receiver may also be a conditional access. + // `(a?.b(x = 1))?.c(y = 1)` + if (VisitPossibleConditionalAccess(node.Receiver, out var receiverStateWhenNotNull)) + { + stateWhenNotNull = receiverStateWhenNotNull; + } + else + { + Unsplit(); + stateWhenNotNull = this.State.Clone(); + } if (node.Receiver.ConstantValue != null && !IsConstantNull(node.Receiver)) { @@ -2767,9 +2776,9 @@ private void VisitConditionalAccess(BoundConditionalAccess node, out TLocalState // We can "know" that `.M0(x = 1)` was evaluated unconditionally but not `M0(y = 1)`. // Therefore we do a VisitPossibleConditionalAccess here which unconditionally includes the "after receiver" state in State // and includes the "after subsequent conditional accesses" in stateWhenNotNull - if (VisitPossibleConditionalAccess(node.AccessExpression, out var innerStateWhenNotNull)) + if (VisitPossibleConditionalAccess(node.AccessExpression, out var firstAccessStateWhenNotNull)) { - stateWhenNotNull = innerStateWhenNotNull; + stateWhenNotNull = firstAccessStateWhenNotNull; } else { @@ -2784,6 +2793,10 @@ private void VisitConditionalAccess(BoundConditionalAccess node, out TLocalState { SetUnreachable(); } + else + { + SetState(stateWhenNotNull); + } // We want to preserve stateWhenNotNull from accesses in the same "chain": // a?.b(out x)?.c(out y); // expected to preserve stateWhenNotNull from both ?.b(out x) and ?.c(out y) @@ -2792,6 +2805,7 @@ private void VisitConditionalAccess(BoundConditionalAccess node, out TLocalState BoundExpression expr = node.AccessExpression; while (expr is BoundConditionalAccess innerCondAccess) { + Debug.Assert(innerCondAccess.Receiver is not (BoundConditionalAccess or BoundConversion)); // we assume that non-conditional accesses can never contain conditional accesses from the same "chain". // that is, we never have to dig through non-conditional accesses to find and handle conditional accesses. VisitRvalue(innerCondAccess.Receiver); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index d7dae718ac9e7..f1addeb01b320 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -3621,13 +3621,67 @@ private TypeWithState InferResultNullability(BinaryOperatorKind operatorKind, Me protected override void VisitBinaryOperatorChildren(ArrayBuilder stack) { var binary = stack.Pop(); - var (leftOperand, leftConversion) = RemoveConversion(binary.Left, includeExplicitConversions: false); - Visit(leftOperand); + + // Only the leftmost operator of a left-associative binary operator chain can learn from a conditional access on the left + // For simplicity, we just special case it here. + // For example, `a?.b(out x) == true` has a conditional access on the left of the operator, + // but `expr == a?.b(out x) == true` has a conditional access on the right of the operator + if (VisitPossibleConditionalAccess(leftOperand, out var conditionalStateWhenNotNull) + && CanPropagateStateWhenNotNull(leftConversion) + && binary.OperatorKind.Operator() is BinaryOperatorKind.Equal or BinaryOperatorKind.NotEqual) + { + Debug.Assert(!IsConditionalState); + var leftType = ResultType; + var (rightOperand, rightConversion) = RemoveConversion(binary.Right, includeExplicitConversions: false); + + // Consider the following two scenarios: + // `a?.b(x = new object()) == c.d(x = null)` // `x` is maybe-null after expression + // `a?.b(x = null) == c.d(x = new object())` // `x` is not-null after expression + + // In this scenario, we visit the RHS twice: + // (1) Once using the single "stateWhenNotNull" after the LHS, in order to update the "stateWhenNotNull" with the effects of the RHS + // (2) Once using the "worst case" state after the LHS for diagnostics and public API + + // After the two visits of the RHS, we may set a conditional state using the state after (1) as the StateWhenTrue and the state after (2) as the StateWhenFalse. + // Depending on whether `==` or `!=` was used, and depending on the value of the RHS, we may then swap the StateWhenTrue with the StateWhenFalse. + + var oldDisableDiagnostics = _disableDiagnostics; + _disableDiagnostics = true; + + var stateAfterLeft = this.State; + SetState(getUnconditionalStateWhenNotNull(rightOperand, conditionalStateWhenNotNull)); + VisitRvalue(rightOperand); + var stateWhenNotNull = this.State; + + _disableDiagnostics = oldDisableDiagnostics; + + // Now visit the right side for public API and diagnostics using the worst-case state from the LHS. + // Note that we do this visit last to try and make sure that the "visit for public API" overwrites walker state recorded during previous visits where possible. + SetState(stateAfterLeft); + var rightType = VisitRvalueWithState(rightOperand); + ReinferBinaryOperatorAndSetResult(leftOperand, leftConversion, leftType, rightOperand, rightConversion, rightType, binary); + if (isKnownNullOrNotNull(rightOperand, rightType)) + { + var isNullConstant = rightOperand.ConstantValue?.IsNull == true; + SetConditionalState(isNullConstant == isEquals(binary) + ? (State, stateWhenNotNull) + : (stateWhenNotNull, State)); + } + + if (stack.Count == 0) + { + return; + } + + leftOperand = binary; + leftConversion = Conversion.Identity; + binary = stack.Pop(); + } while (true) { - if (!learnFromBooleanConstantTest()) + if (!learnFromConditionalAccessOrBoolConstant()) { Unsplit(); // VisitRvalue does this UseRvalueOnly(leftOperand); // drop lvalue part @@ -3645,59 +3699,95 @@ protected override void VisitBinaryOperatorChildren(ArrayBuilder binary.OperatorKind.Operator() == BinaryOperatorKind.Equal; + + static bool isKnownNullOrNotNull(BoundExpression expr, TypeWithState resultType) + { + return resultType.State.IsNotNull() + || expr.ConstantValue is object; + } + + LocalState getUnconditionalStateWhenNotNull(BoundExpression otherOperand, PossiblyConditionalState conditionalStateWhenNotNull) { - if (!IsConditionalState) + LocalState stateWhenNotNull; + if (!conditionalStateWhenNotNull.IsConditionalState) { - return false; + stateWhenNotNull = conditionalStateWhenNotNull.State; } - - if (!leftConversion.IsIdentity) + else if (isEquals(binary) && otherOperand.ConstantValue is { IsBoolean: true, BooleanValue: var boolValue }) { - return false; + // can preserve conditional state from `.TryGetValue` in `dict?.TryGetValue(key, out value) == true`, + // but not in `dict?.TryGetValue(key, out value) != false` + stateWhenNotNull = boolValue ? conditionalStateWhenNotNull.StateWhenTrue : conditionalStateWhenNotNull.StateWhenFalse; } + else + { + stateWhenNotNull = conditionalStateWhenNotNull.StateWhenTrue; + Join(ref stateWhenNotNull, ref conditionalStateWhenNotNull.StateWhenFalse); + } + return stateWhenNotNull; + } - BinaryOperatorKind op = binary.OperatorKind.Operator(); - if (op != BinaryOperatorKind.Equal && op != BinaryOperatorKind.NotEqual) + // Returns true if `binary.Right` was visited by the call. + bool learnFromConditionalAccessOrBoolConstant() + { + if (binary.OperatorKind.Operator() is not (BinaryOperatorKind.Equal or BinaryOperatorKind.NotEqual)) { return false; } - bool isSense; - if (binary.Right.ConstantValue?.IsBoolean == true) + var leftResult = ResultType; + var (rightOperand, rightConversion) = RemoveConversion(binary.Right, includeExplicitConversions: false); + // `true == a?.b(out x)` + if (isKnownNullOrNotNull(leftOperand, leftResult) + && CanPropagateStateWhenNotNull(rightConversion) + && TryVisitConditionalAccess(rightOperand, out var conditionalStateWhenNotNull)) { - UseRvalueOnly(leftOperand); // record result for the left + ReinferBinaryOperatorAndSetResult(leftOperand, leftConversion, leftResult, rightOperand, rightConversion, rightType: ResultType, binary); + + var stateWhenNotNull = getUnconditionalStateWhenNotNull(leftOperand, conditionalStateWhenNotNull); + var isNullConstant = leftOperand.ConstantValue?.IsNull == true; + SetConditionalState(isNullConstant == isEquals(binary) + ? (State, stateWhenNotNull) + : (stateWhenNotNull, State)); - var stateWhenTrue = this.StateWhenTrue.Clone(); - var stateWhenFalse = this.StateWhenFalse.Clone(); + return true; + } + + // can only learn from a bool constant operand here if it's using the built in `bool operator ==(bool left, bool right)` + if (binary.OperatorKind.IsUserDefined()) + { + return false; + } + // `(x != null) == true` + if (IsConditionalState && binary.Right.ConstantValue is { IsBoolean: true } rightConstant) + { + var (stateWhenTrue, stateWhenFalse) = (StateWhenTrue.Clone(), StateWhenFalse.Clone()); Unsplit(); Visit(binary.Right); UseRvalueOnly(binary.Right); // record result for the right - - SetConditionalState(stateWhenTrue, stateWhenFalse); - isSense = (op == BinaryOperatorKind.Equal) == binary.Right.ConstantValue.BooleanValue; + SetConditionalState(isEquals(binary) == rightConstant.BooleanValue + ? (stateWhenTrue, stateWhenFalse) + : (stateWhenFalse, stateWhenTrue)); } - else if (binary.Left.ConstantValue?.IsBoolean == true) + // `true == (x != null)` + else if (binary.Left.ConstantValue is { IsBoolean: true } leftConstant) { Unsplit(); - UseRvalueOnly(leftOperand); // record result for the left - Visit(binary.Right); - UseRvalueOnly(binary.Right); // record result for the right - isSense = (op == BinaryOperatorKind.Equal) == binary.Left.ConstantValue.BooleanValue; + UseRvalueOnly(binary.Right); + if (IsConditionalState && isEquals(binary) != leftConstant.BooleanValue) + { + SetConditionalState(StateWhenFalse, StateWhenTrue); + } } else { return false; } - if (!isSense && IsConditionalState) - { - SetConditionalState(StateWhenFalse, StateWhenTrue); - } - // record result for the binary Debug.Assert(binary.Type.SpecialType == SpecialType.System_Boolean); SetResult(binary, TypeWithState.ForType(binary.Type), TypeWithAnnotations.Create(binary.Type)); @@ -3705,14 +3795,15 @@ bool learnFromBooleanConstantTest() } } - private void AfterLeftChildHasBeenVisited(BoundExpression leftOperand, Conversion leftConversion, BoundBinaryOperator binary) + private void ReinferBinaryOperatorAndSetResult( + BoundExpression leftOperand, + Conversion leftConversion, + TypeWithState leftType, + BoundExpression rightOperand, + Conversion rightConversion, + TypeWithState rightType, + BoundBinaryOperator binary) { - Debug.Assert(!IsConditionalState); - TypeWithState leftType = ResultType; - - var (rightOperand, rightConversion) = RemoveConversion(binary.Right, includeExplicitConversions: false); - var rightType = VisitRvalueWithState(rightOperand); - Debug.Assert(!IsConditionalState); // At this point, State.Reachable may be false for // invalid code such as `s + throw new Exception()`. @@ -3805,6 +3896,37 @@ void visitOperandConversion( var inferredResult = InferResultNullability(binary.OperatorKind, method, binary.Type, leftType, rightType); SetResult(binary, inferredResult, inferredResult.ToTypeWithAnnotations(compilation)); + TypeSymbol? getTypeIfContainingType(TypeSymbol baseType, TypeSymbol? derivedType) + { + if (derivedType is null) + { + return null; + } + derivedType = derivedType.StrippedType(); + var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; + var conversion = _conversions.ClassifyBuiltInConversion(derivedType, baseType, ref discardedUseSiteInfo); + if (conversion.Exists && !conversion.IsExplicit) + { + return derivedType; + } + return null; + } + } + + private void AfterLeftChildHasBeenVisited( + BoundExpression leftOperand, + Conversion leftConversion, + BoundBinaryOperator binary) + { + Debug.Assert(!IsConditionalState); + var leftType = ResultType; + + var (rightOperand, rightConversion) = RemoveConversion(binary.Right, includeExplicitConversions: false); + VisitRvalue(rightOperand); + + var rightType = ResultType; + ReinferBinaryOperatorAndSetResult(leftOperand, leftConversion, leftType, rightOperand, rightConversion, rightType, binary); + BinaryOperatorKind op = binary.OperatorKind.Operator(); if (op == BinaryOperatorKind.Equal || op == BinaryOperatorKind.NotEqual) @@ -3874,22 +3996,6 @@ void splitAndLearnFromNonNullTest(BoundExpression operandComparedToNonNull, bool } slotBuilder.Free(); } - - TypeSymbol? getTypeIfContainingType(TypeSymbol baseType, TypeSymbol? derivedType) - { - if (derivedType is null) - { - return null; - } - derivedType = derivedType.StrippedType(); - var discardedUseSiteInfo = CompoundUseSiteInfo.Discarded; - var conversion = _conversions.ClassifyBuiltInConversion(derivedType, baseType, ref discardedUseSiteInfo); - if (conversion.Exists && !conversion.IsExplicit) - { - return derivedType; - } - return null; - } } /// @@ -4248,9 +4354,6 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly } } - // PROTOTYPE(improved-definite-assignment): - // this is not used outside 'VisitPossibleConditionalAccess' yet, but is expected to be used - // when refactoring 'VisitBinaryOperatorChildren' /// /// Visits a node only if it is a conditional access. /// Returns 'true' if and only if the node was visited. @@ -4258,7 +4361,7 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly private bool TryVisitConditionalAccess(BoundExpression node, out PossiblyConditionalState stateWhenNotNull) { var (operand, conversion) = RemoveConversion(node, includeExplicitConversions: true); - if (operand is not BoundConditionalAccess access || !CanPropagateStateWhenNotNull(access, conversion)) + if (operand is not BoundConditionalAccess access || !CanPropagateStateWhenNotNull(conversion)) { stateWhenNotNull = default; return false; @@ -4291,28 +4394,33 @@ private bool TryVisitConditionalAccess(BoundExpression node, out PossiblyConditi /// /// Unconditionally visits an expression and returns the "state when not null" for the expression. /// - private void VisitPossibleConditionalAccess(BoundExpression node, out PossiblyConditionalState stateWhenNotNull) + private bool VisitPossibleConditionalAccess(BoundExpression node, out PossiblyConditionalState stateWhenNotNull) { - if (!TryVisitConditionalAccess(node, out stateWhenNotNull)) + if (TryVisitConditionalAccess(node, out stateWhenNotNull)) { - // in case we *didn't* have a conditional access, the only thing we learn in the "state when not null" - // is that the top-level expression was non-null. - Visit(node); - stateWhenNotNull = PossiblyConditionalState.Create(this); - var slot = MakeSlot(node); - if (slot > -1) + return true; + } + + // in case we *didn't* have a conditional access, the only thing we learn in the "state when not null" + // is that the top-level expression was non-null. + Visit(node); + stateWhenNotNull = PossiblyConditionalState.Create(this); + + (node, _) = RemoveConversion(node, includeExplicitConversions: true); + var slot = MakeSlot(node); + if (slot > -1) + { + if (IsConditionalState) { - if (IsConditionalState) - { - LearnFromNonNullTest(slot, ref stateWhenNotNull.StateWhenTrue); - LearnFromNonNullTest(slot, ref stateWhenNotNull.StateWhenFalse); - } - else - { - LearnFromNonNullTest(slot, ref stateWhenNotNull.State); - } + LearnFromNonNullTest(slot, ref stateWhenNotNull.StateWhenTrue); + LearnFromNonNullTest(slot, ref stateWhenNotNull.StateWhenFalse); + } + else + { + LearnFromNonNullTest(slot, ref stateWhenNotNull.State); } } + return false; } private void VisitConditionalAccess(BoundConditionalAccess node, out PossiblyConditionalState stateWhenNotNull) @@ -4320,7 +4428,11 @@ private void VisitConditionalAccess(BoundConditionalAccess node, out PossiblyCon Debug.Assert(!IsConditionalState); var receiver = node.Receiver; - VisitRvalue(receiver); + + // handle scenarios like `(a?.b)?.c()` + VisitPossibleConditionalAccess(receiver, out stateWhenNotNull); + Unsplit(); + _currentConditionalReceiverVisitResult = _visitResult; var previousConditionalAccessSlot = _lastConditionalAccessSlot; @@ -4346,6 +4458,7 @@ private void VisitConditionalAccess(BoundConditionalAccess node, out PossiblyCon // In the other branch, the receiver is known to be non-null. LearnFromNullTest(receiver, ref savedState); makeAndAdjustReceiverSlot(receiver); + SetPossiblyConditionalState(stateWhenNotNull); } // We want to preserve stateWhenNotNull from accesses in the same "chain": @@ -4357,6 +4470,7 @@ private void VisitConditionalAccess(BoundConditionalAccess node, out PossiblyCon { // we assume that non-conditional accesses can never contain conditional accesses from the same "chain". // that is, we never have to dig through non-conditional accesses to find and handle conditional accesses. + Debug.Assert(innerCondAccess.Receiver is not (BoundConditionalAccess or BoundConversion)); VisitRvalue(innerCondAccess.Receiver); _currentConditionalReceiverVisitResult = _visitResult; makeAndAdjustReceiverSlot(innerCondAccess.Receiver); @@ -4415,12 +4529,15 @@ private void VisitConditionalAccess(BoundConditionalAccess node, out PossiblyCon void makeAndAdjustReceiverSlot(BoundExpression receiver) { var slot = MakeSlot(receiver); + if (slot > -1) + LearnFromNonNullTest(slot, ref State); + + // given `a?.b()`, when `a` is a nullable value type, + // the conditional receiver for `?.b()` must be linked to `a.Value`, not to `a`. if (slot > 0 && receiver.Type?.IsNullableType() == true) slot = GetNullableOfTValueSlot(receiver.Type, slot, out _); _lastConditionalAccessSlot = slot; - if (slot > -1) - LearnFromNonNullTest(slot, ref State); } } diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs index 30dde86b974e0..b0f7ea782df76 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs @@ -4061,6 +4061,203 @@ public void M4(C? c) ); } + [Fact] + public void EqualsCondAccess_19() + { + var source = @" +#nullable enable + +class C +{ + public int M0(object obj) => 1; + + public void M1(C? c) + { + int x, y, z; + _ = c?.M0(x = y = z = 1) != x // 1 + ? y.ToString() // 2 + : z.ToString(); + } + + public void M2(C? c) + { + int x, y, z; + _ = x != c?.M0(x = y = z = 1) // 3 + ? y.ToString() // 4 + : z.ToString(); + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (11,37): error CS0165: Use of unassigned local variable 'x' + // _ = c?.M0(x = y = z = 1) != x // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(11, 37), + // (12,15): error CS0165: Use of unassigned local variable 'y' + // ? y.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(12, 15), + // (19,13): error CS0165: Use of unassigned local variable 'x' + // _ = x != c?.M0(x = y = z = 1) // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(19, 13), + // (20,15): error CS0165: Use of unassigned local variable 'y' + // ? y.ToString() // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(20, 15) + ); + } + + [Fact] + public void EqualsCondAccess_LeftCondAccess() + { + var source = @" +#nullable enable + +class C +{ + public C M0(object x) => this; + + public void M1(C? c) + { + int w, x, y, z; + _ = (c?.M0(w = x = 1))?.M0(y = z = 1) != null + ? w.ToString() + y.ToString() + : x.ToString() + z.ToString(); // 1, 2 + } + + public void M2(C? c) + { + int w, x, y, z; + _ = (c?.M0(w = x = 1)?.M0(y = z = 1))?.GetHashCode() != null + ? w.ToString() + y.ToString() + : x.ToString() + z.ToString(); // 3, 4 + } + + public void M3(C? c) + { + int x, y; + _ = ((object?)c?.M0(x = y = 1))?.GetHashCode() != null + ? x.ToString() + y.ToString() + : x.ToString() + y.ToString(); // 5, 6 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (13,15): error CS0165: Use of unassigned local variable 'x' + // : x.ToString() + z.ToString(); // 1, 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(13, 15), + // (13,30): error CS0165: Use of unassigned local variable 'z' + // : x.ToString() + z.ToString(); // 1, 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "z").WithArguments("z").WithLocation(13, 30), + // (21,15): error CS0165: Use of unassigned local variable 'x' + // : x.ToString() + z.ToString(); // 3, 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(21, 15), + // (21,30): error CS0165: Use of unassigned local variable 'z' + // : x.ToString() + z.ToString(); // 3, 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "z").WithArguments("z").WithLocation(21, 30), + // (29,15): error CS0165: Use of unassigned local variable 'x' + // : x.ToString() + y.ToString(); // 5, 6 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(29, 15), + // (29,30): error CS0165: Use of unassigned local variable 'y' + // : x.ToString() + y.ToString(); // 5, 6 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(29, 30) + ); + } + + [Theory] + [InlineData("")] + [InlineData("T t, int i")] + public void EqualsCondAccess_BadUserDefinedConversion(string conversionParams) + { + var source = @" +#nullable enable + +struct T +{ + public static implicit operator S(" + conversionParams + @") => new S(); // 1 + public T M0(out int x, out int y) { x = 42; y = 42; return this; } +} + +struct S +{ + public static bool operator ==(S left, S right) => false; + public static bool operator !=(S left, S right) => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public void M1(T? t) + { + int x, y; + _ = t?.M0(out x, out y) != new S() // 2 + ? x.ToString() // 3 + : y.ToString(); // 4 + } + + public void M2(T? t) + { + int x, y; + _ = t?.M0(out x, out y) == new S() // 5 + ? x.ToString() // 6 + : y.ToString(); // 7 + } + + public void M3(T? t) + { + int x, y; + _ = new S() != t?.M0(out x, out y) // 8 + ? x.ToString() // 9 + : y.ToString(); // 10 + } + + public void M4(T? t) + { + int x, y; + _ = new S() == t?.M0(out x, out y) // 11 + ? x.ToString() // 12 + : y.ToString(); // 13 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (6,38): error CS1019: Overloadable unary operator expected + // public static implicit operator S(T t, int i) => new S(); // 1 + Diagnostic(ErrorCode.ERR_OvlUnaryOperatorExpected, "(" + conversionParams + ")").WithLocation(6, 38), + // (20,13): error CS0019: Operator '!=' cannot be applied to operands of type 'T?' and 'S' + // _ = t?.M0(out x, out y) != new S() // 2 + Diagnostic(ErrorCode.ERR_BadBinaryOps, "t?.M0(out x, out y) != new S()").WithArguments("!=", "T?", "S").WithLocation(20, 13), + // (21,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(21, 15), + // (22,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(22, 15), + // (28,13): error CS0019: Operator '==' cannot be applied to operands of type 'T?' and 'S' + // _ = t?.M0(out x, out y) == new S() // 5 + Diagnostic(ErrorCode.ERR_BadBinaryOps, "t?.M0(out x, out y) == new S()").WithArguments("==", "T?", "S").WithLocation(28, 13), + // (29,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 6 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(29, 15), + // (30,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 7 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(30, 15), + // (36,13): error CS0019: Operator '!=' cannot be applied to operands of type 'S' and 'T?' + // _ = new S() != t?.M0(out x, out y) // 8 + Diagnostic(ErrorCode.ERR_BadBinaryOps, "new S() != t?.M0(out x, out y)").WithArguments("!=", "S", "T?").WithLocation(36, 13), + // (37,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 9 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(37, 15), + // (38,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 10 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(38, 15), + // (44,13): error CS0019: Operator '==' cannot be applied to operands of type 'S' and 'T?' + // _ = new S() == t?.M0(out x, out y) // 11 + Diagnostic(ErrorCode.ERR_BadBinaryOps, "new S() == t?.M0(out x, out y)").WithArguments("==", "S", "T?").WithLocation(44, 13), + // (45,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 12 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(45, 15), + // (46,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 13 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(46, 15) + ); + } + [Fact] public void IsBool() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 3b11ab11c0140..61368c6df903a 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -28901,7 +28901,7 @@ static public void M(C c) { if (c.field?.field.IsKind() == true) { - c.field.field.ToString(); // 1 + c.field.field.ToString(); } } @@ -28909,7 +28909,7 @@ static public void M2(C c) { if (true == c.field?.field.IsKind()) { - c.field.field.ToString(); // 2 + c.field.field.ToString(); } } @@ -28932,16 +28932,7 @@ public static bool IsKind([NotNullWhen(true)] this C? c) } ", NotNullWhenAttributeDefinition }); - // Note: when we're done analyzing `c.field?.field.IsKind()` we have an unconditional state. - // If we wanted to analyze this properly, we would probably need a WhenTrue/WhenFalse/WhenNull split. - c.VerifyDiagnostics( - // (12,13): warning CS8602: Dereference of a possibly null reference. - // c.field.field.ToString(); // 1 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c.field.field").WithLocation(12, 13), - // (20,13): warning CS8602: Dereference of a possibly null reference. - // c.field.field.ToString(); // 2 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c.field.field").WithLocation(20, 13) - ); + c.VerifyDiagnostics(); } [Fact] @@ -47139,6 +47130,1634 @@ void M5(object? x) ); } + [Theory] + [InlineData("[NotNullWhen(true)] out object? obj")] + [InlineData("[MaybeNullWhen(false)] out object obj")] + public void EqualsCondAccess_NotNullWhenTrue_RightBoolConstant(string param) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class C +{ + bool M0(" + param + @") { obj = new object(); return true; } + + static void M1(C? c) + { + _ = c?.M0(out var obj) == true + ? obj.ToString() + : obj.ToString(); // 1, 2 + } + + static void M2(C? c) + { + _ = c?.M0(out var obj) == false + ? obj.ToString() // 3 + : obj.ToString(); // 4, 5 + } + + static void M3(C? c) + { + _ = c?.M0(out var obj) != true + ? obj.ToString() // 6, 7 + : obj.ToString(); // 8 + } + + static void M4(C? c) + { + _ = c?.M0(out var obj) != false + ? obj.ToString() // 9, 10 + : obj.ToString(); // 11 + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (12,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 1, 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(12, 15), + // (12,15): error CS0165: Use of unassigned local variable 'obj' + // : obj.ToString(); // 1, 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(12, 15), + // (18,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(18, 15), + // (19,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 4, 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(19, 15), + // (19,15): error CS0165: Use of unassigned local variable 'obj' + // : obj.ToString(); // 4, 5 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(19, 15), + // (25,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 6, 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(25, 15), + // (25,15): error CS0165: Use of unassigned local variable 'obj' + // ? obj.ToString() // 6, 7 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(25, 15), + // (26,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 8 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(26, 15), + // (32,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 9, 10 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(32, 15), + // (32,15): error CS0165: Use of unassigned local variable 'obj' + // ? obj.ToString() // 9, 10 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(32, 15), + // (33,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 11 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(33, 15) + ); + } + + [Theory] + [InlineData("[NotNullWhen(true)] out object? obj")] + [InlineData("[MaybeNullWhen(false)] out object obj")] + public void EqualsCondAccess_NotNullWhenTrue_LeftBoolConstant(string param) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class C +{ + bool M0(" + param + @") { obj = new object(); return true; } + + static void M1(C? c) + { + _ = true == c?.M0(out var obj) + ? obj.ToString() + : obj.ToString(); // 1, 2 + } + + static void M2(C? c) + { + _ = false == c?.M0(out var obj) + ? obj.ToString() // 3 + : obj.ToString(); // 4, 5 + } + + static void M3(C? c) + { + _ = true != c?.M0(out var obj) + ? obj.ToString() // 6, 7 + : obj.ToString(); // 8 + } + + static void M4(C? c) + { + _ = false != c?.M0(out var obj) + ? obj.ToString() // 9, 10 + : obj.ToString(); // 11 + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (12,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 1, 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(12, 15), + // (12,15): error CS0165: Use of unassigned local variable 'obj' + // : obj.ToString(); // 1, 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(12, 15), + // (18,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(18, 15), + // (19,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 4, 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(19, 15), + // (19,15): error CS0165: Use of unassigned local variable 'obj' + // : obj.ToString(); // 4, 5 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(19, 15), + // (25,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 6, 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(25, 15), + // (25,15): error CS0165: Use of unassigned local variable 'obj' + // ? obj.ToString() // 6, 7 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(25, 15), + // (26,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 8 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(26, 15), + // (32,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 9, 10 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(32, 15), + // (32,15): error CS0165: Use of unassigned local variable 'obj' + // ? obj.ToString() // 9, 10 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(32, 15), + // (33,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 11 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(33, 15) + ); + } + + [Theory] + [InlineData("[NotNullWhen(true)] out object? obj")] + [InlineData("[MaybeNullWhen(false)] out object obj")] + public void EqualsCondAccess_NotNullWhenTrue_RightBool(string param) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class C +{ + bool M0(" + param + @") { obj = new object(); return true; } + + static void M1(C? c, bool b) + { + _ = c?.M0(out var obj1) == b + ? obj1.ToString() // 1 + : """"; + } + + static void M2(C? c, bool b) + { + _ = c?.M0(out var obj2) == b + ? """" + : obj2.ToString(); // 2, 3 + } + + static void M3(C? c, bool b) + { + _ = c?.M0(out var obj3) != b + ? obj3.ToString() // 4, 5 + : """"; + } + + static void M4(C? c, bool b) + { + _ = c?.M0(out var obj4) != b + ? """" + : obj4.ToString(); // 6 + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (11,15): warning CS8602: Dereference of a possibly null reference. + // ? obj1.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj1").WithLocation(11, 15), + // (19,15): warning CS8602: Dereference of a possibly null reference. + // : obj2.ToString(); // 2, 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj2").WithLocation(19, 15), + // (19,15): error CS0165: Use of unassigned local variable 'obj2' + // : obj2.ToString(); // 2, 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj2").WithArguments("obj2").WithLocation(19, 15), + // (25,15): warning CS8602: Dereference of a possibly null reference. + // ? obj3.ToString() // 4, 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj3").WithLocation(25, 15), + // (25,15): error CS0165: Use of unassigned local variable 'obj3' + // ? obj3.ToString() // 4, 5 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj3").WithArguments("obj3").WithLocation(25, 15), + // (33,15): warning CS8602: Dereference of a possibly null reference. + // : obj4.ToString(); // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj4").WithLocation(33, 15) + ); + } + + [Theory] + [InlineData("[NotNullWhen(true)] out object? obj")] + [InlineData("[MaybeNullWhen(false)] out object obj")] + public void EqualsCondAccess_NotNullWhenTrue_LeftBool(string param) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class C +{ + bool M0(" + param + @") { obj = new object(); return true; } + + static void M1(C? c, bool b) + { + _ = b == c?.M0(out var obj1) + ? obj1.ToString() // 1 + : """"; + } + + static void M2(C? c, bool b) + { + _ = b == c?.M0(out var obj2) + ? """" + : obj2.ToString(); // 2, 3 + } + + static void M3(C? c, bool b) + { + _ = b != c?.M0(out var obj3) + ? obj3.ToString() // 4, 5 + : """"; + } + + static void M4(C? c, bool b) + { + _ = b != c?.M0(out var obj4) + ? """" + : obj4.ToString(); // 6 + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (11,15): warning CS8602: Dereference of a possibly null reference. + // ? obj1.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj1").WithLocation(11, 15), + // (19,15): warning CS8602: Dereference of a possibly null reference. + // : obj2.ToString(); // 2, 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj2").WithLocation(19, 15), + // (19,15): error CS0165: Use of unassigned local variable 'obj2' + // : obj2.ToString(); // 2, 3 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj2").WithArguments("obj2").WithLocation(19, 15), + // (25,15): warning CS8602: Dereference of a possibly null reference. + // ? obj3.ToString() // 4, 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj3").WithLocation(25, 15), + // (25,15): error CS0165: Use of unassigned local variable 'obj3' + // ? obj3.ToString() // 4, 5 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj3").WithArguments("obj3").WithLocation(25, 15), + // (33,15): warning CS8602: Dereference of a possibly null reference. + // : obj4.ToString(); // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj4").WithLocation(33, 15) + ); + } + + [Theory] + [InlineData("[NotNullWhen(true)] out object? obj")] + [InlineData("[MaybeNullWhen(false)] out object obj")] + public void EqualsCondAccess_NotNullWhenTrue_RightNullableBool(string param) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class C +{ + bool M0(" + param + @") { obj = new object(); return true; } + + static void M1(C? c) + { + _ = c?.M0(out var obj) == null + ? obj.ToString() // 1, 2 + : obj.ToString(); // 3 + } + + static void M2(C? c) + { + _ = c?.M0(out var obj) != null + ? obj.ToString() // 4 + : obj.ToString(); // 5, 6 + } + + static void M3(C? c, bool? b) + { + _ = c?.M0(out var obj1) == b + ? obj1.ToString() // 7, 8 + : """"; // 9, 10 + + _ = c?.M0(out var obj2) == b + ? """" // 7, 8 + : obj2.ToString(); // 9, 10 + } + + static void M4(C? c, bool? b) + { + _ = c?.M0(out var obj1) != b + ? obj1.ToString() // 11, 12 + : """"; // 9, 10 + + _ = c?.M0(out var obj2) != b + ? """" // 7, 8 + : obj2.ToString(); // 13, 14 + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (11,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 1, 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(11, 15), + // (11,15): error CS0165: Use of unassigned local variable 'obj' + // ? obj.ToString() // 1, 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(11, 15), + // (12,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(12, 15), + // (18,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(18, 15), + // (19,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 5, 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(19, 15), + // (19,15): error CS0165: Use of unassigned local variable 'obj' + // : obj.ToString(); // 5, 6 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(19, 15), + // (25,15): warning CS8602: Dereference of a possibly null reference. + // ? obj1.ToString() // 7, 8 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj1").WithLocation(25, 15), + // (25,15): error CS0165: Use of unassigned local variable 'obj1' + // ? obj1.ToString() // 7, 8 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj1").WithArguments("obj1").WithLocation(25, 15), + // (30,15): warning CS8602: Dereference of a possibly null reference. + // : obj2.ToString(); // 9, 10 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj2").WithLocation(30, 15), + // (30,15): error CS0165: Use of unassigned local variable 'obj2' + // : obj2.ToString(); // 9, 10 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj2").WithArguments("obj2").WithLocation(30, 15), + // (36,15): warning CS8602: Dereference of a possibly null reference. + // ? obj1.ToString() // 11, 12 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj1").WithLocation(36, 15), + // (36,15): error CS0165: Use of unassigned local variable 'obj1' + // ? obj1.ToString() // 11, 12 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj1").WithArguments("obj1").WithLocation(36, 15), + // (41,15): warning CS8602: Dereference of a possibly null reference. + // : obj2.ToString(); // 13, 14 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj2").WithLocation(41, 15), + // (41,15): error CS0165: Use of unassigned local variable 'obj2' + // : obj2.ToString(); // 13, 14 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj2").WithArguments("obj2").WithLocation(41, 15) + ); + } + + [Theory] + [InlineData("[NotNullWhen(true)] out object? obj")] + [InlineData("[MaybeNullWhen(false)] out object obj")] + public void EqualsCondAccess_NotNullWhenTrue_LeftNullableBool(string param) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class C +{ + bool M0(" + param + @") { obj = new object(); return true; } + + static void M1(C? c) + { + _ = null == c?.M0(out var obj) + ? obj.ToString() // 1, 2 + : obj.ToString(); // 3 + } + + static void M2(C? c) + { + _ = null != c?.M0(out var obj) + ? obj.ToString() // 4 + : obj.ToString(); // 5, 6 + } + + static void M3(C? c, bool? b) + { + _ = b == c?.M0(out var obj1) + ? obj1.ToString() // 7, 8 + : """"; // 9, 10 + + _ = b == c?.M0(out var obj2) + ? """" // 7, 8 + : obj2.ToString(); // 9, 10 + } + + static void M4(C? c, bool? b) + { + _ = b != c?.M0(out var obj1) + ? obj1.ToString() // 11, 12 + : """"; // 9, 10 + + _ = b != c?.M0(out var obj2) + ? """" // 7, 8 + : obj2.ToString(); // 13, 14 + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (11,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 1, 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(11, 15), + // (11,15): error CS0165: Use of unassigned local variable 'obj' + // ? obj.ToString() // 1, 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(11, 15), + // (12,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(12, 15), + // (18,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(18, 15), + // (19,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 5, 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(19, 15), + // (19,15): error CS0165: Use of unassigned local variable 'obj' + // : obj.ToString(); // 5, 6 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj").WithArguments("obj").WithLocation(19, 15), + // (25,15): warning CS8602: Dereference of a possibly null reference. + // ? obj1.ToString() // 7, 8 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj1").WithLocation(25, 15), + // (25,15): error CS0165: Use of unassigned local variable 'obj1' + // ? obj1.ToString() // 7, 8 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj1").WithArguments("obj1").WithLocation(25, 15), + // (30,15): warning CS8602: Dereference of a possibly null reference. + // : obj2.ToString(); // 9, 10 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj2").WithLocation(30, 15), + // (30,15): error CS0165: Use of unassigned local variable 'obj2' + // : obj2.ToString(); // 9, 10 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj2").WithArguments("obj2").WithLocation(30, 15), + // (36,15): warning CS8602: Dereference of a possibly null reference. + // ? obj1.ToString() // 11, 12 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj1").WithLocation(36, 15), + // (36,15): error CS0165: Use of unassigned local variable 'obj1' + // ? obj1.ToString() // 11, 12 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj1").WithArguments("obj1").WithLocation(36, 15), + // (41,15): warning CS8602: Dereference of a possibly null reference. + // : obj2.ToString(); // 13, 14 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj2").WithLocation(41, 15), + // (41,15): error CS0165: Use of unassigned local variable 'obj2' + // : obj2.ToString(); // 13, 14 + Diagnostic(ErrorCode.ERR_UseDefViolation, "obj2").WithArguments("obj2").WithLocation(41, 15) + ); + } + + /// Ported from . + [Theory] + [InlineData("b")] + [InlineData("true")] + [InlineData("false")] + public void EqualsCondAccess_01(string operand) + { + var source = @" +class C +{ + public bool M0(out object x) { x = 42; return true; } + + public void M1(C? c, object? x, bool b) + { + _ = c?.M0(out x) == " + operand + @" + ? x.ToString() + : x.ToString(); // 1 + } + + public void M2(C? c, object? x, bool b) + { + _ = c?.M0(out x) != " + operand + @" + ? x.ToString() // 2 + : x.ToString(); + } + + public void M3(C? c, object? x, bool b) + { + _ = " + operand + @" == c?.M0(out x) + ? x.ToString() + : x.ToString(); // 3 + } + + public void M4(C? c, object? x, bool b) + { + _ = " + operand + @" != c?.M0(out x) + ? x.ToString() // 4 + : x.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 15), + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(16, 15), + // (24,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(24, 15), + // (30,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(30, 15) + ); + } + + /// Ported from . + [Theory] + [InlineData("i")] + [InlineData("42")] + public void EqualsCondAccess_02(string operand) + { + var source = @" +class C +{ + public int M0(out object x) { x = 1; return 0; } + + public void M1(C? c, object? x, int i) + { + _ = c?.M0(out x) == " + operand + @" + ? x.ToString() + : x.ToString(); // 1 + } + + public void M2(C? c, object? x, int i) + { + _ = c?.M0(out x) != " + operand + @" + ? x.ToString() // 2 + : x.ToString(); + } + + public void M3(C? c, object? x, int i) + { + _ = " + operand + @" == c?.M0(out x) + ? x.ToString() + : x.ToString(); // 3 + } + + public void M4(C? c, object? x, int i) + { + _ = " + operand + @" != c?.M0(out x) + ? x.ToString() // 4 + : x.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 15), + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(16, 15), + // (24,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(24, 15), + // (30,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(30, 15) + ); + } + + /// Ported from . + [Theory] + [InlineData("object?")] + [InlineData("int?")] + [InlineData("bool?")] + public void EqualsCondAccess_03(string returnType) + { + var source = @" +class C +{ + public " + returnType + @" M0(out object x) { x = 42; return null; } + + public void M1(C? c, object? x) + { + _ = c?.M0(out x) != null + ? x.ToString() + : x.ToString(); // 1 + } + + public void M2(C? c, object? x) + { + _ = c?.M0(out x) == null + ? x.ToString() // 2 + : x.ToString(); + } + + public void M3(C? c, object? x) + { + _ = null != c?.M0(out x) + ? x.ToString() + : x.ToString(); // 3 + } + + public void M4(C? c, object? x) + { + _ = null == c?.M0(out x) + ? x.ToString() // 4 + : x.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 15), + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(16, 15), + // (24,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(24, 15), + // (30,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(30, 15) + ); + } + + /// Ported from . + [Theory] + [InlineData("bool", "false")] + [InlineData("int", "1")] + [InlineData("object", "1")] + public void EqualsCondAccess_04_01(string returnType, string returnValue) + { + var source = @" +class C +{ + public " + returnType + @" M0(out object x) { x = 42; return " + returnValue + @"; } + + public void M1(C? c, object? x, object? y) + { + _ = c?.M0(out x) == c!.M0(out y) + ? x.ToString() + y.ToString() + : x.ToString() + y.ToString(); // 1 + } + + public void M2(C? c, object? x, object? y) + { + _ = c?.M0(out x) != c!.M0(out y) + ? x.ToString() + y.ToString() // 2 + : x.ToString() + y.ToString(); + } + + public void M3(C? c, object? x, object? y) + { + _ = c!.M0(out x) == c?.M0(out y) + ? x.ToString() + y.ToString() + : x.ToString() + y.ToString(); // 3 + } + + public void M4(C? c, object? x, object? y) + { + _ = c!.M0(out x) != c?.M0(out y) + ? x.ToString() + y.ToString() // 4 + : x.ToString() + y.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString() + y.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 15), + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() + y.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(16, 15), + // (24,30): warning CS8602: Dereference of a possibly null reference. + // : x.ToString() + y.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(24, 30), + // (30,30): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() + y.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(30, 30) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_04_02() + { + var source = @" +class C +{ + public object? M0(out object x) { x = 42; return null; } + + public void M1(C? c, object? x, object? y) + { + _ = c?.M0(out x) == c!.M0(out y) + ? x.ToString() + y.ToString() // 1 + : x.ToString() + y.ToString(); // 2 + } + + public void M2(C? c, object? x, object? y) + { + _ = c?.M0(out x) != c!.M0(out y) + ? x.ToString() + y.ToString() // 3 + : x.ToString() + y.ToString(); // 4 + } + + public void M3(C? c, object? x, object? y) + { + _ = c!.M0(out x) == c?.M0(out y) + ? x.ToString() + y.ToString() // 5 + : x.ToString() + y.ToString(); // 6 + } + + public void M4(C? c, object? x, object? y) + { + _ = c!.M0(out x) != c?.M0(out y) + ? x.ToString() + y.ToString() // 7 + : x.ToString() + y.ToString(); // 8 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (9,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() + y.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(9, 15), + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString() + y.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 15), + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() + y.ToString() // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(16, 15), + // (17,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString() + y.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(17, 15), + // (23,30): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() + y.ToString() // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(23, 30), + // (24,30): warning CS8602: Dereference of a possibly null reference. + // : x.ToString() + y.ToString(); // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(24, 30), + // (30,30): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() + y.ToString() // 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(30, 30), + // (31,30): warning CS8602: Dereference of a possibly null reference. + // : x.ToString() + y.ToString(); // 8 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(31, 30) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_04_03() + { + var source = @" +class C +{ + public object M0(object? x) => new object(); + + public void M1(C? c, object? x) + { + _ = c?.M0(x = null) == c!.M0(x = new object()) + ? x.ToString() + : x.ToString(); + } + + public void M2(C? c, object? x) + { + _ = c?.M0(x = new object()) != c!.M0(x = null) + ? x.ToString() // 1 + : x.ToString(); // 2 + } + + public void M3(C? c, object? x) + { + _ = c!.M0(x = new object()) != c?.M0(x = null) + ? x.ToString() // 3 + : x.ToString(); // 4 + } + + public void M4(C? c, object? x) + { + _ = c!.M0(x = null) != c?.M0(x = new object()) + ? x.ToString() // 5 + : x.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(16, 15), + // (17,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(17, 15), + // (23,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(23, 15), + // (24,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(24, 15), + // (30,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(30, 15) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_04_04() + { + var source = @" +class C +{ + public object M0(object? x) => new object(); + + public void M1(C? c, object? x, object? y) + { + _ = c?.M0(x = new object()) == c!.M0(y = x) + ? y.ToString() + : y.ToString(); // 1 + } + + public void M2(C? c, object? x, object? y) + { + _ = c?.M0(x = new object()) != c!.M0(y = x) + ? y.ToString() // 2 + : y.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : y.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(10, 15), + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? y.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(16, 15) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_05() + { + var source = @" +class C +{ + public static bool operator ==(C? left, C? right) => false; + public static bool operator !=(C? left, C? right) => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public C? M0(out object x) { x = 42; return this; } + + public void M1(C? c, object? x) + { + _ = c?.M0(out x) != null + ? x.ToString() + : x.ToString(); // 1 + } + + public void M2(C? c, object? x) + { + _ = c?.M0(out x) == null + ? x.ToString() // 2 + : x.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (15,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 15), + // (21,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(21, 15) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_06() + { + var source = @" +class C +{ + public C? M0(out object x) { x = 42; return this; } + + public void M1(C c, object? x, object? y) + { + _ = c.M0(out x)?.M0(out y) != null + ? x.ToString() + y.ToString() + : x.ToString() + y.ToString(); // 1 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (10,30): warning CS8602: Dereference of a possibly null reference. + // : x.ToString() + y.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(10, 30) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_07() + { + var source = @" +struct S +{ + public static bool operator ==(S left, S right) => false; + public static bool operator !=(S left, S right) => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public S M0(out object x) { x = 42; return this; } + + public void M1(S? s, object? x) + { + _ = s?.M0(out x) != null + ? x.ToString() + : x.ToString(); // 1 + } + + public void M2(S? s, object? x) + { + _ = s?.M0(out x) == null + ? x.ToString() // 2 + : x.ToString(); + } + + public void M3(S? s, object? x) + { + _ = null != s?.M0(out x) + ? x.ToString() + : x.ToString(); // 3 + } + + public void M4(S? s, object? x) + { + _ = null == s?.M0(out x) + ? x.ToString() // 4 + : x.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (15,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 15), + // (21,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(21, 15), + // (29,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(29, 15), + // (35,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(35, 15) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_08() + { + var source = @" +#nullable enable + +struct S +{ + public static bool operator ==(S left, S right) => false; + public static bool operator !=(S left, S right) => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public S M0(out object x) { x = 42; return this; } + + public void M1(S? s, object? x) + { + _ = s?.M0(out x) != new S() + ? x.ToString() // 1 + : x.ToString(); + } + + public void M2(S? s, object? x) + { + _ = s?.M0(out x) == new S() + ? x.ToString() + : x.ToString(); // 2 + } + + public void M3(S? s, object? x) + { + _ = new S() != s?.M0(out x) + ? x.ToString() // 3 + : x.ToString(); + } + + public void M4(S? s, object? x) + { + _ = new S() == s?.M0(out x) + ? x.ToString() + : x.ToString(); // 4 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(16, 15), + // (24,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(24, 15), + // (30,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(30, 15), + // (38,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(38, 15) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_09() + { + var source = @" +struct S +{ + public static bool operator ==(S left, S right) => false; + public static bool operator !=(S left, S right) => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public S M0(out object x) { x = 42; return this; } + + public void M1(S? s, object? x) + { + _ = s?.M0(out x) != s + ? x.ToString() // 1 + : x.ToString(); // 2 + } + + public void M2(S? s, object? x) + { + _ = s?.M0(out x) == s + ? x.ToString() // 3 + : x.ToString(); // 4 + } + + public void M3(S? s, object? x) + { + _ = s != s?.M0(out x) + ? x.ToString() // 5 + : x.ToString(); // 6 + } + + public void M4(S? s, object? x) + { + _ = s == s?.M0(out x) + ? x.ToString() // 7 + : x.ToString(); // 8 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (14,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(14, 15), + // (15,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 15), + // (21,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(21, 15), + // (22,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(22, 15), + // (28,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(28, 15), + // (29,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(29, 15), + // (35,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(35, 15), + // (36,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 8 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(36, 15) + ); + } + + /// Ported from . + [Theory] + [InlineData("S? left, S? right")] + [InlineData("S? left, S right")] + public void EqualsCondAccess_10(string operatorParameters) + { + var source = @" +struct S +{ + public static bool operator ==(" + operatorParameters + @") => false; + public static bool operator !=(" + operatorParameters + @") => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public S M0(out object x) { x = 42; return this; } + + public void M1(S? s, object? x) + { + _ = s?.M0(out x) != new S() + ? x.ToString() // 1 + : x.ToString(); + } + + public void M2(S? s, object? x) + { + _ = s?.M0(out x) == new S() + ? x.ToString() + : x.ToString(); // 2 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (14,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(14, 15), + // (22,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(22, 15) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_11() + { + var source = @" +struct T +{ + public static implicit operator S(T t) => new S(); + public T M0(out object x) { x = 42; return this; } +} + +struct S +{ + public static bool operator ==(S left, S right) => false; + public static bool operator !=(S left, S right) => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public void M1(T? t, object? x) + { + _ = t?.M0(out x) != new S() + ? x.ToString() // 1 + : x.ToString(); + } + + public void M2(T? t, object? x) + { + _ = t?.M0(out x) == new S() + ? x.ToString() + : x.ToString(); // 2 + } + + public void M3(T? t, object? x) + { + _ = new S() != t?.M0(out x) + ? x.ToString() // 3 + : x.ToString(); + } + + public void M4(T? t, object? x) + { + _ = new S() == t?.M0(out x) + ? x.ToString() + : x.ToString(); // 4 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (18,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(18, 15), + // (26,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(26, 15), + // (32,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(32, 15), + // (40,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(40, 15) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_12() + { + var source = @" +class C +{ + int? M0(object obj) => null; + + void M1(C? c, object? x, object? obj) + { + _ = (c?.M0(x = 0) == (int?)obj) + ? x.ToString() // 1 + : x.ToString(); // 2 + } + + void M2(C? c, object? x, object? obj) + { + _ = (c?.M0(x = 0) == (int?)null) + ? x.ToString() // 3 + : x.ToString(); + } + + void M3(C? c, object? x, object? obj) + { + _ = (c?.M0(x = 0) == null) + ? x.ToString() // 4 + : x.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (9,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(9, 15), + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 15), + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(16, 15), + // (23,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(23, 15) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_13() + { + var source = @" +class C +{ + long? M0(object obj) => null; + void M(C? c, object? x, int i) + { + _ = (c?.M0(x = 0) == i) + ? x.ToString() + : x.ToString(); // 1 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (9,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(9, 15) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_14() + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class C +{ + long? M0(object obj) => null; + + void M1(C? c, object? x, int? i) + { + _ = (c?.M0(x = 0) == i) + ? x.ToString() // 1 + : x.ToString(); // 2 + } + + void M2(C? c, object? x, int? i) + { + if (i is null) throw null!; + + _ = (c?.M0(x = 0) == i) + ? x.ToString() + : x.ToString(); // 3 + } + + void M3(C? c, object? x, [DisallowNull] int? i) + { + _ = (c?.M0(x = 0) == i) + ? x.ToString() + : x.ToString(); // 4 + } +} +"; + CreateNullableCompilation(new[] { source, DisallowNullAttributeDefinition }).VerifyDiagnostics( + // (11,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(11, 15), + // (12,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(12, 15), + // (21,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(21, 15), + // (28,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(28, 15) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_15() + { + var source = @" +class C +{ + C M0(object obj) => this; + void M(C? c, object? x) + { + _ = ((object?)c?.M0(x = 0) != null) + ? x.ToString() + : x.ToString(); // 1 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (9,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(9, 15) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_16() + { + var source = @" +class C +{ + void M(object? x) + { + _ = ""a""?.Equals(x = 0) == true + ? x.ToString() + : x.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_17() + { + var source = @" +class C +{ + void M(C? c, object? x, object? y) + { + _ = (c?.Equals(x = 0), c?.Equals(y = 0)) == (true, true) + ? x.ToString() // 1 + : y.ToString(); // 2 + } +} +"; + // https://github.com/dotnet/roslyn/issues/50980 + CreateNullableCompilation(source).VerifyDiagnostics( + // (7,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(7, 15), + // (8,15): warning CS8602: Dereference of a possibly null reference. + // : y.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(8, 15) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_18() + { + var source = @" +class C +{ + public static bool operator ==(C? left, C? right) => false; + public static bool operator !=(C? left, C? right) => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public C M0(out object x) { x = 42; return this; } + + public void M1(C? c, object? x) + { + _ = c?.M0(out x) != null + ? x.ToString() + : x.ToString(); // 1 + } + + public void M2(C? c, object? x) + { + _ = c?.M0(out x) == null + ? x.ToString() // 2 + : x.ToString(); + } + + public void M3(C? c, object? x) + { + _ = null != c?.M0(out x) + ? x.ToString() + : x.ToString(); // 3 + } + + public void M4(C? c, object? x) + { + _ = null == c?.M0(out x) + ? x.ToString() // 4 + : x.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (15,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 15), + // (21,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(21, 15), + // (29,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(29, 15), + // (35,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(35, 15) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_19() + { + var source = @" +class C +{ + public string M0(object obj) => obj.ToString(); + + public void M1(C? c, object? x, object? y) + { + _ = c?.M0(x = y = 1) != x.ToString() // 1 + ? y.ToString() // 2 + : y.ToString(); + } + + public void M2(C? c, object? x, object? y) + { + _ = x.ToString() != c?.M0(x = y = 1) // 3 + ? y.ToString() // 4 + : y.ToString(); + } + + public void M3(C? c, string? x) + { + _ = c?.M0(x = ""a"") != x + ? x.ToString() // 5 + : x.ToString(); // 6 + } + + public void M4(C? c, string? x) + { + _ = x != c?.M0(x = ""a"") + ? x.ToString() // 7 + : x.ToString(); // 8 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (8,33): warning CS8602: Dereference of a possibly null reference. + // _ = c?.M0(x = y = 1) != x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 33), + // (9,15): warning CS8602: Dereference of a possibly null reference. + // ? y.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(9, 15), + // (15,13): warning CS8602: Dereference of a possibly null reference. + // _ = x.ToString() != c?.M0(x = y = 1) // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(15, 13), + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? y.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(16, 15), + // (23,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(23, 15), + // (24,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(24, 15), + // (30,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(30, 15), + // (31,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 8 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(31, 15) + ); + } + + [Fact] + public void EqualsBoolConstant_UserDefinedOperator_BoolRight() + { + var source = @" +class C +{ + public static bool operator ==(C? left, bool right) => false; + public static bool operator !=(C? left, bool right) => false; + public override bool Equals(object obj) => false; + public override int GetHashCode() => 0; + + public void M1(C? c, object? x) + { + _ = c == (x != null) + ? x.ToString() // 1 + : x.ToString(); // 2 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (12,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(12, 15), + // (13,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(13, 15) + ); + } + + /// Ported from . + [Fact] + public void EqualsCondAccess_LeftCondAccess() + { + var source = @" +class C +{ + public C M0(object x) => this; + + public void M1(C? c, object? x, object? y) + { + _ = (c?.M0(x = 1))?.M0(y = 1) != null + ? c.ToString() + x.ToString() + y.ToString() + : c.ToString() + x.ToString() + y.ToString(); // 1, 2, 3 + } + + public void M2(C? c, object? x, object? y, object? z) + { + _ = (c?.M0(x = 1)?.M0(y = 1))?.M0(z = 1) != null + ? c.ToString() + x.ToString() + y.ToString() + z.ToString() + : c.ToString() + x.ToString() + y.ToString() + z.ToString(); // 4, 5, 6, 7 + } + + public void M3(C? c, object? x, object? y) + { + _ = ((object?)c?.M0(x = 1))?.Equals(y = 1) != null + ? c.ToString() + x.ToString() + y.ToString() + : c.ToString() + x.ToString() + y.ToString(); // 8, 9, 10 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : c.ToString() + x.ToString() + y.ToString(); // 1, 2, 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(10, 15), + // (10,30): warning CS8602: Dereference of a possibly null reference. + // : c.ToString() + x.ToString() + y.ToString(); // 1, 2, 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 30), + // (10,45): warning CS8602: Dereference of a possibly null reference. + // : c.ToString() + x.ToString() + y.ToString(); // 1, 2, 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(10, 45), + // (17,15): warning CS8602: Dereference of a possibly null reference. + // : c.ToString() + x.ToString() + y.ToString() + z.ToString(); // 4, 5, 6, 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(17, 15), + // (17,30): warning CS8602: Dereference of a possibly null reference. + // : c.ToString() + x.ToString() + y.ToString() + z.ToString(); // 4, 5, 6, 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(17, 30), + // (17,45): warning CS8602: Dereference of a possibly null reference. + // : c.ToString() + x.ToString() + y.ToString() + z.ToString(); // 4, 5, 6, 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(17, 45), + // (17,60): warning CS8602: Dereference of a possibly null reference. + // : c.ToString() + x.ToString() + y.ToString() + z.ToString(); // 4, 5, 6, 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "z").WithLocation(17, 60), + // (24,15): warning CS8602: Dereference of a possibly null reference. + // : c.ToString() + x.ToString() + y.ToString(); // 8, 9, 10 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(24, 15), + // (24,30): warning CS8602: Dereference of a possibly null reference. + // : c.ToString() + x.ToString() + y.ToString(); // 8, 9, 10 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(24, 30), + // (24,45): warning CS8602: Dereference of a possibly null reference. + // : c.ToString() + x.ToString() + y.ToString(); // 8, 9, 10 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(24, 45) + ); + } + [Fact] public void ConditionalOperator_OperandConditionalState() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs index ccba49124268e..6566129e0fbb1 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionPerfTests.cs @@ -617,5 +617,44 @@ public void NullableAnalysisNestedExpressionsInLocalFunction() // C c = new C() Diagnostic(ErrorCode.ERR_InsufficientStack, "new").WithLocation(10, 15)); } + + [ConditionalFact(typeof(NoIOperationValidation), typeof(IsRelease))] + public void NullableAnalysis_CondAccess_ComplexRightSide() + { + var source1 = @" +#nullable enable +object? x = null; +C? c = null; +if ( +"; + var source2 = @" + ) +{ +} + +class C +{ + public bool M(object? obj) => false; +} +"; + var sourceBuilder = new StringBuilder(); + sourceBuilder.Append(source1); + for (var i = 0; i < 15; i++) + { + sourceBuilder.AppendLine($" c?.M(x = {i}) == ("); + } + sourceBuilder.AppendLine(" c!.M(x)"); + + sourceBuilder.Append(" "); + for (var i = 0; i < 15; i++) + { + sourceBuilder.Append(")"); + } + + sourceBuilder.Append(source2); + + var comp = CreateCompilation(sourceBuilder.ToString()); + comp.VerifyDiagnostics(); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/NullablePublicAPITests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/NullablePublicAPITests.cs index f1ae95650620f..2afbe863c3509 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/NullablePublicAPITests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/NullablePublicAPITests.cs @@ -5058,5 +5058,96 @@ public void FreshSemanticModelDoesNotCacheSwitchExpressionInput() model = comp.GetSemanticModel(tree); AssertEx.Equal("System.String!", model.GetTypeInfo(switchExpressionInput).Type.ToTestDisplayString(includeNonNullable: true)); } + + [Fact] + public void CondAccessLeft_NonConstantRight_FlowState_01() + { + var source = @" +#nullable enable + +class C +{ + public object? M(object? obj) => false; + + public static void M1(C? c, object? x) + { + if (c?.M(x = ""a"") == x) + { + x.ToString(); // 1 + } + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (12,13): warning CS8602: Dereference of a possibly null reference. + // x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(12, 13)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var binaryRight = tree.GetRoot().DescendantNodes().OfType().Single().Right; + Assert.Equal("System.Object?", model.GetTypeInfo(binaryRight).Type.ToTestDisplayString(includeNonNullable: true)); + } + + [Fact] + public void CondAccessLeft_NonConstantRight_FlowState_02() + { + var source = @" +#nullable enable + +class C +{ + public object? M(object? obj) => false; + + public static void M1(C? c) + { + object? x = ""a""; + if (c?.M(x = null) == x) + { + x.ToString(); // 1 + } + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (13,13): warning CS8602: Dereference of a possibly null reference. + // x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(13, 13)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var binaryRight = tree.GetRoot().DescendantNodes().OfType().Single().Right; + Assert.Equal("System.Object?", model.GetTypeInfo(binaryRight).Type.ToTestDisplayString(includeNonNullable: true)); + } + + [Fact] + public void CondAccessLeft_NonConstantRight_FlowState_03() + { + var source = @" +#nullable enable + +class C +{ + public bool M(object? obj) => false; + + public static void M1(C? c, object? x) + { + if (c?.M(x = ""a"") == c!.M(x)) + { + x.ToString(); + } + } +} +"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var binaryRightArgument = tree.GetRoot().DescendantNodes().OfType().Single().Right.DescendantNodes().OfType().Single().Expression; + Assert.Equal("System.Object?", model.GetTypeInfo(binaryRightArgument).Type.ToTestDisplayString(includeNonNullable: true)); + } } } From a4429361d470c2834e691440e42b1757c9dcc120 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 8 Jun 2021 14:25:44 -0700 Subject: [PATCH 8/9] Fix region analysis of == containing ?. (#53687) --- .../Portable/FlowAnalysis/AbstractFlowPass.cs | 28 +++++++- .../FlowAnalysis/RegionAnalysisTests.cs | 70 +++++++++++++++++++ 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index b1c62589638b5..f23c2903b760c 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -2363,8 +2363,32 @@ protected virtual void VisitBinaryOperatorChildren(ArrayBuilder oldState = NonMonotonicState; + NonMonotonicState = ReachableBottomState(); + + VisitRvalue(binary.Right); + + var tempStateValue = NonMonotonicState.Value; + Join(ref stateWhenNotNull, ref tempStateValue); + if (oldState.HasValue) + { + var oldStateValue = oldState.Value; + Join(ref oldStateValue, ref tempStateValue); + oldState = oldStateValue; + } + + NonMonotonicState = oldState; + } + else + { + VisitRvalue(binary.Right); + Meet(ref stateWhenNotNull, ref State); + } + var isNullConstant = binary.Right.ConstantValue?.IsNull == true; SetConditionalState(isNullConstant == isEquals(binary) ? (State, stateWhenNotNull) diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs index 2125f2a8967c5..a29e0821f920e 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs @@ -2546,6 +2546,76 @@ public string M() Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.CapturedOutside)); } + [Theory] + [InlineData("c?.M0(x = 0)")] + [InlineData("c!.M0(x = 0)")] + public void CondAccess_Equals_DataFlowsOut_01(string leftOperand) + { + var dataFlowAnalysis = CompileAndAnalyzeDataFlowExpression(@" +#nullable enable +class C +{ + bool M0(object? obj) => false; + + public static void M(C? c) + { + int x = 0; + if (" + leftOperand + @" == /**/c!.M0(x = 0)/**/) + { + x.ToString(); + } + + x.ToString(); + } +} +"); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.VariablesDeclared)); + Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysis.AlwaysAssigned)); + Assert.Equal("c", GetSymbolNamesJoined(dataFlowAnalysis.DataFlowsIn)); + Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysis.DataFlowsOut)); + Assert.Equal("c", GetSymbolNamesJoined(dataFlowAnalysis.ReadInside)); + Assert.Equal("c, x", GetSymbolNamesJoined(dataFlowAnalysis.ReadOutside)); + Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysis.WrittenInside)); + Assert.Equal("c, x", GetSymbolNamesJoined(dataFlowAnalysis.WrittenOutside)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.Captured)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.CapturedInside)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.CapturedOutside)); + } + + [Theory] + [InlineData("c?.M0(x = 0)")] + [InlineData("c!.M0(x = 0)")] + public void CondAccess_Equals_DataFlowsOut_02(string leftOperand) + { + var dataFlowAnalysis = CompileAndAnalyzeDataFlowExpression(@" +#nullable enable +class C +{ + bool M0(object? obj) => false; + + public static void M(C? c) + { + int x = 0; + if (" + leftOperand + @" == /**/c!.M0(x = 0)/**/) + { + x.ToString(); + } + } +} +"); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.VariablesDeclared)); + Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysis.AlwaysAssigned)); + Assert.Equal("c", GetSymbolNamesJoined(dataFlowAnalysis.DataFlowsIn)); + Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysis.DataFlowsOut)); + Assert.Equal("c", GetSymbolNamesJoined(dataFlowAnalysis.ReadInside)); + Assert.Equal("c, x", GetSymbolNamesJoined(dataFlowAnalysis.ReadOutside)); + Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysis.WrittenInside)); + Assert.Equal("c, x", GetSymbolNamesJoined(dataFlowAnalysis.WrittenOutside)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.Captured)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.CapturedInside)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysis.CapturedOutside)); + } + #endregion #region "Statements" From 2cca737de3fb70bf1bf4dc04462916dbf4c0953b Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Thu, 10 Jun 2021 12:32:32 -0700 Subject: [PATCH 9/9] Improved nullable 'is' analysis (#53311) --- .../Portable/FlowAnalysis/AbstractFlowPass.cs | 41 +- .../Portable/FlowAnalysis/NullableWalker.cs | 20 +- .../FlowAnalysis/NullableWalker_Patterns.cs | 29 +- .../Test/Semantic/FlowAnalysis/FlowTests.cs | 108 ++ .../Semantics/NullableReferenceTypesTests.cs | 985 ++++++++++++++++++ 5 files changed, 1145 insertions(+), 38 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index f23c2903b760c..df4d53bdf8607 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -969,15 +969,9 @@ public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node if (VisitPossibleConditionalAccess(node.Expression, out var stateWhenNotNull)) { Debug.Assert(!IsConditionalState); - switch (isTopLevelNonNullTest(pattern)) - { - case true: - SetConditionalState(stateWhenNotNull, State); - break; - case false: - SetConditionalState(State, stateWhenNotNull); - break; - } + SetConditionalState(patternMatchesNull(pattern) + ? (State, stateWhenNotNull) + : (stateWhenNotNull, State)); } else if (IsConditionalState) { @@ -1018,10 +1012,7 @@ public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node return node; - // Returns `true` if the pattern only matches if the top-level input is not null. - // Returns `false` if the pattern only matches if the top-level input is null. - // Otherwise, returns `null`. - static bool? isTopLevelNonNullTest(BoundPattern pattern) + static bool patternMatchesNull(BoundPattern pattern) { switch (pattern) { @@ -1031,30 +1022,26 @@ public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node case BoundRelationalPattern: case BoundDeclarationPattern { IsVar: false }: case BoundConstantPattern { ConstantValue: { IsNull: false } }: - return true; - case BoundConstantPattern { ConstantValue: { IsNull: true } }: return false; + case BoundConstantPattern { ConstantValue: { IsNull: true } }: + return true; case BoundNegatedPattern negated: - return !isTopLevelNonNullTest(negated.Negated); + return !patternMatchesNull(negated.Negated); case BoundBinaryPattern binary: if (binary.Disjunction) { // `a?.b(out x) is null or C` - // both subpatterns must have the same null test for the test to propagate out - var leftNullTest = isTopLevelNonNullTest(binary.Left); - return leftNullTest is null ? null : - leftNullTest != isTopLevelNonNullTest(binary.Right) ? null : - leftNullTest; + // pattern matches null if either subpattern matches null + var leftNullTest = patternMatchesNull(binary.Left); + return patternMatchesNull(binary.Left) || patternMatchesNull(binary.Right); } - // `a?.b(out x) is not null and var c` - // if any pattern performs a test, we know that test applies at the top level - // note that if the tests are different, e.g. `null and not null`, - // the pattern is a contradiction, so we expect an error diagnostic - return isTopLevelNonNullTest(binary.Left) ?? isTopLevelNonNullTest(binary.Right); + // `a?.b out x is not null and var c` + // pattern matches null only if both subpatterns match null + return patternMatchesNull(binary.Left) && patternMatchesNull(binary.Right); case BoundDeclarationPattern { IsVar: true }: case BoundDiscardPattern: - return null; + return true; default: throw ExceptionUtilities.UnexpectedValue(pattern.Kind); } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 0c484ec15d1b1..62e4c9f9f9116 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4004,6 +4004,7 @@ void splitAndLearnFromNonNullTest(BoundExpression operandComparedToNonNull, bool /// Get all nested conditional slots for those sub-expressions. For example in a?.b?.c we'll set a, b, and c. /// Only returns slots for tracked expressions. /// + /// https://github.com/dotnet/roslyn/issues/53397 This method should potentially be removed. private void GetSlotsToMarkAsNotNullable(BoundExpression operand, ArrayBuilder slotBuilder) { Debug.Assert(operand != null); @@ -4098,7 +4099,6 @@ private static void MarkSlotsAsNotNull(ArrayBuilder slots, ref LocalState s } } - // PROTOTYPE(improved-definite-assignment): eventually, GetSlotsToMarkAsNotNullable should be removed private void LearnFromNonNullTest(BoundExpression expression, ref LocalState state) { if (expression.Kind == BoundKind.AwaitableValuePlaceholder) @@ -9336,11 +9336,23 @@ private TypeWithState InferResultNullabilityOfBinaryLogicalOperator(BoundExpress var operand = node.Operand; var typeExpr = node.TargetType; - VisitRvalue(operand); + VisitPossibleConditionalAccess(operand, out var conditionalStateWhenNotNull); + Unsplit(); + + LocalState stateWhenNotNull; + if (!conditionalStateWhenNotNull.IsConditionalState) + { + stateWhenNotNull = conditionalStateWhenNotNull.State; + } + else + { + stateWhenNotNull = conditionalStateWhenNotNull.StateWhenTrue; + Join(ref stateWhenNotNull, ref conditionalStateWhenNotNull.StateWhenFalse); + } + Debug.Assert(node.Type.SpecialType == SpecialType.System_Boolean); - Split(); - LearnFromNonNullTest(operand, ref StateWhenTrue); + SetConditionalState(stateWhenNotNull, State); if (typeExpr.Type?.SpecialType == SpecialType.System_Object) { LearnFromNullTest(operand, ref StateWhenFalse); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs index b5be970e66123..2cee9d87b6f88 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs @@ -21,7 +21,6 @@ internal sealed partial class NullableWalker /// Learn something about the input from a test of a given expression against a given pattern. The given /// state is updated to note that any slots that are tested against `null` may be null. /// - /// true if there is a top-level explicit null check private void LearnFromAnyNullPatterns( BoundExpression expression, BoundPattern pattern) @@ -216,7 +215,7 @@ protected override LocalState VisitSwitchStatementDispatch(BoundSwitchStatement DeclareLocals(section.Locals); } - var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState); + var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState, stateWhenNotNullOpt: null); foreach (var section in node.SwitchSections) { foreach (var label in section.SwitchLabels) @@ -287,7 +286,8 @@ public PossiblyConditionalState Clone() SyntaxNode node, BoundDecisionDag decisionDag, BoundExpression expression, - TypeWithState expressionType) + TypeWithState expressionType, + PossiblyConditionalState? stateWhenNotNullOpt) { // We reuse the slot at the beginning of a switch (or is-pattern expression), pretending that we are // not copying the input to evaluate the patterns. In this way we infer non-nullability of the original @@ -512,7 +512,18 @@ public PossiblyConditionalState Clone() break; case BoundDagValueTest t: Debug.Assert(t.Value != ConstantValue.Null); - if (inputSlot > 0) + // When we compare `bool?` inputs to bool constants, we follow a graph roughly like the following: + // [0]: t0 != null ? [1] : [5] + // [1]: t1 = (bool)t0; [2] + // [2] (this node): t1 == boolConstant ? [3] : [4] + // ...(remaining states) + if (stateWhenNotNullOpt is { } stateWhenNotNull + && t.Input.Source is BoundDagTypeEvaluation { Input: { IsOriginalInput: true } }) + { + SetPossiblyConditionalState(stateWhenNotNull); + Split(); + } + else if (inputSlot > 0) { learnFromNonNullTest(inputSlot, ref this.StateWhenTrue); } @@ -600,6 +611,10 @@ public PossiblyConditionalState Clone() void learnFromNonNullTest(int inputSlot, ref LocalState state) { + if (stateWhenNotNullOpt is { } stateWhenNotNull && inputSlot == originalInputSlot) + { + state = CloneAndUnsplit(ref stateWhenNotNull); + } LearnFromNonNullTest(inputSlot, ref state); if (originalInputMap.TryGetValue(inputSlot, out var expression)) LearnFromNonNullTest(expression, ref state); @@ -732,7 +747,7 @@ private void VisitSwitchExpressionCore(BoundSwitchExpression node, bool inferTyp Visit(node.Expression); var expressionState = ResultType; - var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState); + var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState, stateWhenNotNullOpt: null); var endState = UnreachableState(); if (!node.ReportedNotExhaustive && node.DefaultLabel != null && @@ -840,9 +855,9 @@ public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node Debug.Assert(!IsConditionalState); LearnFromAnyNullPatterns(node.Expression, node.Pattern); VisitPatternForRewriting(node.Pattern); - Visit(node.Expression); + var hasStateWhenNotNull = VisitPossibleConditionalAccess(node.Expression, out var conditionalStateWhenNotNull); var expressionState = ResultType; - var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState); + var labelStateMap = LearnFromDecisionDag(node.Syntax, node.DecisionDag, node.Expression, expressionState, hasStateWhenNotNull ? conditionalStateWhenNotNull : null); var trueState = labelStateMap.TryGetValue(node.IsNegated ? node.WhenFalseLabel : node.WhenTrueLabel, out var s1) ? s1.state : UnreachableState(); var falseState = labelStateMap.TryGetValue(node.IsNegated ? node.WhenTrueLabel : node.WhenFalseLabel, out var s2) ? s2.state : UnreachableState(); labelStateMap.Free(); diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs index b0f7ea782df76..f8affa3db6c22 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs @@ -4893,6 +4893,114 @@ void M1(C? c) ); } + [Fact] + public void IsCondAccess_07() + { + var source = @" +#nullable enable +class C +{ + bool M0(object obj) => false; + + void M1(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is true or false + ? x.ToString() + : y.ToString(); // 1 + } + + void M2(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is not (true or false) + ? x.ToString() // 2 + : y.ToString(); + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (12,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(12, 15), + // (19,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(19, 15) + ); + } + + [Fact] + public void IsCondAccess_08() + { + var source = @" +#nullable enable +class C +{ + bool M0(object obj) => false; + + void M1(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is null or false + ? x.ToString() // 1 + : y.ToString(); + } + + void M2(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is not (true or null) + ? x.ToString() + : y.ToString(); // 2 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (11,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(11, 15), + // (20,15): error CS0165: Use of unassigned local variable 'y' + // : y.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(20, 15) + ); + } + + [Fact] + public void IsCondAccess_09() + { + var source = @" +#nullable enable +class C +{ + bool M0(object obj) => false; + + void M1(C? c) + { + int x, y; + _ = c?.M0(x = y = 0) is var z + ? x.ToString() // 1 + : y.ToString(); // unreachable + } + + void M2(C? c) + { + int x, y; + _ = c?.M0(x = 0) is var z + ? x.ToString() // 2 + : y.ToString(); // unreachable + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (11,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(11, 15), + // (19,15): error CS0165: Use of unassigned local variable 'x' + // ? x.ToString() // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(19, 15) + ); + } + [WorkItem(545352, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545352")] [Fact] public void UseDefViolationInDelegateInSwitchWithGoto() diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 46ca05c9d5c50..a5024d340e46d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -48694,6 +48694,991 @@ public void M1(C? c, object? x) ); } + /// Ported from . + [Fact] + public void IsCondAccess_01() + { + var source = @" +class C +{ + C M0(object obj) => this; + + void M1(C? c, object? x) + { + _ = c?.M0(x = 0) is C + ? x.ToString() + : x.ToString(); // 1 + } + + void M2(C? c, object? x) + { + _ = c?.Equals(x = 0) is bool + ? x.ToString() + : x.ToString(); // 2 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 15), + // (17,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(17, 15) + ); + } + + /// Ported from . + [Fact] + public void IsCondAccess_02() + { + var source = @" +class C +{ + C M0(object obj) => this; + + void M1(C? c, object? x) + { + _ = c?.M0(x = 0) is (_) + ? x.ToString() // 1 + : x.ToString(); // unreachable + } + + void M2(C? c, object? x) + { + _ = c?.M0(x = 0) is var y + ? x.ToString() // 2 + : x.ToString(); // unreachable + } + + void M3(C? c, object? x) + { + _ = c?.M0(x = 0) is { } + ? x.ToString() + : x.ToString(); // 3 + } + + void M4(C? c, object? x) + { + _ = c?.M0(x = 0) is { } c1 + ? x.ToString() + : x.ToString(); // 4 + } + + void M5(C? c, object? x) + { + _ = c?.M0(x = 0) is C c1 + ? x.ToString() + : x.ToString(); // 5 + } + + void M6(C? c, object? x) + { + _ = c?.M0(x = 0) is not null + ? x.ToString() + : x.ToString(); // 6 + } + + void M7(C? c, object? x) + { + _ = c?.M0(x = 0) is not C + ? x.ToString() // 7 + : x.ToString(); + } + + void M8(C? c, object? x) + { + _ = c?.M0(x = 0) is null + ? x.ToString() // 8 + : x.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (9,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(9, 15), + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(16, 15), + // (24,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(24, 15), + // (31,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(31, 15), + // (38,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(38, 15), + // (45,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(45, 15), + // (51,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(51, 15), + // (58,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 8 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(58, 15) + ); + } + + /// Ported from . + [Fact] + public void IsCondAccess_03() + { + var source = @" +#nullable enable + +class C +{ + (C, C) M0(object obj) => (this, this); + + void M1(C? c, object? x) + { + _ = c?.M0(x = 0) is (_, _) + ? x.ToString() + : x.ToString(); // 1 + } + + void M2(C? c, object? x) + { + _ = c?.M0(x = 0) is not null + ? x.ToString() + : x.ToString(); // 2 + } + + void M3(C? c, object? x) + { + _ = c?.M0(x = 0) is (not null, null) + ? x.ToString() + : x.ToString(); // 3 + } + + void M4(C? c, object? x) + { + _ = c?.M0(x = 0) is null + ? x.ToString() // 4 + : x.ToString(); + } + + void M5(C? c, object? x, object? y) + { + _ = (c?.M0(x = 0), c?.M0(y = 0)) is (not null, not null) + ? x.ToString() // 5 + : y.ToString(); // 6 + } +} +"; + // note: "state when not null" is not tracked when pattern matching against tuples containing conditional accesses. + CreateCompilation(source).VerifyDiagnostics( + // (12,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(12, 15), + // (19,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(19, 15), + // (26,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(26, 15), + // (32,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(32, 15), + // (39,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(39, 15), + // (40,15): warning CS8602: Dereference of a possibly null reference. + // : y.ToString(); // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(40, 15) + ); + } + + /// Ported from . + [Fact] + public void IsCondAccess_04() + { + var source = @" +#pragma warning disable 8794 // An expression always matches the provided pattern + +class C +{ + C M0(object obj) => this; + + void M1(C? c, object? x) + { + _ = c?.M0(x = 0) is null or not null + ? x.ToString() // 1 + : x.ToString(); // unreachable + } + + void M2(C? c, object? x) + { + _ = c?.M0(x = 0) is C or null + ? x.ToString() // 2 + : x.ToString(); // unreachable + } + + void M3(C? c, object? x) + { + _ = c?.M0(x = 0) is not null and C + ? x.ToString() + : x.ToString(); // 3 + } + + void M4(C? c, object? x) + { + _ = c?.M0(x = 0) is null + ? x.ToString() // 4 + : x.ToString(); + } + + void M5(C? c, object? x) + { + _ = c?.M0(x = 0) is not (C or { }) + ? x.ToString() // 5 + : x.ToString(); + } + + void M6(C? c, object? x) + { + _ = c?.M0(x = 0) is _ and C + ? x.ToString() + : x.ToString(); // 6 + } + + void M7(C? c, object? x) + { + _ = c?.M0(x = 0) is C and _ + ? x.ToString() + : x.ToString(); // 7 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (11,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(11, 15), + // (18,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(18, 15), + // (26,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(26, 15), + // (32,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(32, 15), + // (39,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(39, 15), + // (47,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(47, 15), + // (54,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(54, 15) + ); + } + + /// Ported from . + [Theory] + [InlineData("int")] + [InlineData("int?")] + public void IsCondAccess_05(string returnType) + { + var source = @" +class C +{ + " + returnType + @" M0(object obj) => 1; + + void M1(C? c, object? x) + { + _ = c?.M0(x = 0) is 1 + ? x.ToString() + : x.ToString(); // 1 + } + + void M2(C? c, object? x) + { + _ = c?.M0(x = 0) is > 10 + ? x.ToString() + : x.ToString(); // 2 + } + + void M3(C? c, object? x) + { + _ = c?.M0(x = 0) is > 10 or < 0 + ? x.ToString() + : x.ToString(); // 3 + } + + void M4(C? c, object? x) + { + _ = c?.M0(x = 0) is 1 or 2 + ? x.ToString() + : x.ToString(); // 4 + } + + void M5(C? c, object? x) + { + _ = c?.M0(x = 0) is null + ? x.ToString() // 5 + : x.ToString(); + } + + void M6(C? c, object? x) + { + _ = c?.M0(x = 0) is not null + ? x.ToString() + : x.ToString(); // 6 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 15), + // (17,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(17, 15), + // (24,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(24, 15), + // (31,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(31, 15), + // (37,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(37, 15), + // (45,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(45, 15) + ); + } + + /// Ported from . + [Fact] + public void IsCondAccess_06() + { + var source = @" +class C +{ + int M0(object obj) => 1; + + void M1(C? c, object? x) + { + _ = c?.M0(x = 0) is not null is true + ? x.ToString() + : x.ToString(); // 1 + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 15) + ); + } + + /// Ported from . + [Fact] + public void IsCondAccess_07() + { + var source = @" +class C +{ + bool M0(object obj) => false; + + void M1(C? c, object? x) + { + _ = c?.M0(x = 0) is true or false + ? x.ToString() + : x.ToString(); // 1 + } + + void M2(C? c, object? x) + { + _ = c?.M0(x = 0) is not (true or false) + ? x.ToString() // 2 + : x.ToString(); + } +} +"; + CreateNullableCompilation(source).VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 15), + // (16,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(16, 15) + ); + } + + /// Ported from . + [Fact] + public void IsCondAccess_08() + { + var source = @" +#nullable enable +class C +{ + bool M0(object obj) => false; + + void M1(C? c, object? x) + { + _ = c?.M0(x = 0) is null or false + ? x.ToString() // 1 + : x.ToString(); + } + + void M2(C? c, object? x) + { + _ = c?.M0(x = 0) is not (true or null) + ? x.ToString() + : x.ToString(); // 2 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 15), + // (18,15): warning CS8602: Dereference of a possibly null reference. + // : x.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(18, 15) + ); + } + + /// Ported from . + [Fact] + public void IsCondAccess_09() + { + var source = @" +#nullable enable +class C +{ + bool M0(object obj) => false; + + void M1(C? c, object? x) + { + _ = c?.M0(x = 0) is var z + ? x.ToString() // 1 + : x.ToString(); // unreachable + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (10,15): warning CS8602: Dereference of a possibly null reference. + // ? x.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 15) + ); + } + + [Fact] + public void IsCondAccess_10() + { + var source = @" +#nullable enable +class C +{ + object M0() => """"; + + void M1(C? c) + { + _ = c?.M0() is { } z + ? z.ToString() + : z.ToString(); // 1 + } + + void M2(C? c) + { + _ = c?.M0() is """" and { } z + ? z.ToString() + : z.ToString(); // 2 + } + + void M3(C? c) + { + _ = (string?)c?.M0() is 42 and { } z // 3 + ? z.ToString() + : z.ToString(); // 4 + } + + void M4(C? c) + { + _ = c?.M0() is string z + ? z.ToString() + : z.ToString(); // 5 + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (11,15): error CS0165: Use of unassigned local variable 'z' + // : z.ToString(); // 1 + Diagnostic(ErrorCode.ERR_UseDefViolation, "z").WithArguments("z").WithLocation(11, 15), + // (18,15): error CS0165: Use of unassigned local variable 'z' + // : z.ToString(); // 2 + Diagnostic(ErrorCode.ERR_UseDefViolation, "z").WithArguments("z").WithLocation(18, 15), + // (23,33): error CS0029: Cannot implicitly convert type 'int' to 'string' + // _ = (string?)c?.M0() is 42 and { } z // 3 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "42").WithArguments("int", "string").WithLocation(23, 33), + // (25,15): error CS0165: Use of unassigned local variable 'z' + // : z.ToString(); // 4 + Diagnostic(ErrorCode.ERR_UseDefViolation, "z").WithArguments("z").WithLocation(25, 15), + // (32,15): error CS0165: Use of unassigned local variable 'z' + // : z.ToString(); // 5 + Diagnostic(ErrorCode.ERR_UseDefViolation, "z").WithArguments("z").WithLocation(32, 15) + ); + } + + [Theory] + [InlineData("[NotNullWhen(true)] out object? obj")] + [InlineData("[MaybeNullWhen(false)] out object obj")] + public void IsCondAccess_NotNullWhenTrue_01(string param) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class C +{ + bool M0(" + param + @") { obj = new object(); return true; } + + static void M1(C? c, object? obj) + { + _ = c?.M0(out obj) is true + ? obj.ToString() + : obj.ToString(); // 1 + } + + static void M2(C? c, object? obj) + { + _ = c?.M0(out obj) is false + ? obj.ToString() // 2 + : obj.ToString(); // 3 + } + + static void M3(C? c, object? obj) + { + _ = c?.M0(out obj) is not true // `is null or false` + ? obj.ToString() // 4 + : obj.ToString(); // 5 + } + + static void M4(C? c, object? obj) + { + _ = c?.M0(out obj) is not false // `is null or true` + ? obj.ToString() // 6 + : obj.ToString(); // 7 + } + + static void M5(C? c, object? obj) + { + _ = c?.M0(out obj) is not (true or null) // `is false` + ? obj.ToString() // 8 + : obj.ToString(); // 9 + } + + static void M6(C? c, object? obj) + { + _ = c?.M0(out obj) is not (false or null) // `is true` + ? obj.ToString() + : obj.ToString(); // 10 + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (12,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(12, 15), + // (18,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(18, 15), + // (19,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(19, 15), + // (25,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(25, 15), + // (32,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(32, 15), + // (33,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(33, 15), + // (39,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 8 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(39, 15), + // (40,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 9 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(40, 15), + // (47,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 10 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(47, 15) + ); + } + + [Theory] + [InlineData("[NotNullWhen(true)] out object? obj")] + [InlineData("[MaybeNullWhen(false)] out object obj")] + public void IsCondAccess_NotNullWhenTrue_02(string param) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class C +{ + bool M0(" + param + @") { obj = new object(); return true; } + + static void M1(C? c, object? obj) + { + _ = c?.M0(out obj) is true and 1 // 1 + ? obj.ToString() // 2 + : obj.ToString(); // 3 + } + + static void M2(C? c, object? obj) + { + _ = c?.M0(out obj) is true and var x + ? obj.ToString() + : obj.ToString(); // 4 + } + + static void M3(C? c, object? obj) + { + _ = c?.M0(out obj) is _ and true + ? obj.ToString() + : obj.ToString(); // 5 + } + + static void M4(C? c, object? obj) + { + _ = c?.M0(out obj) is true and bool b + ? obj.ToString() + : obj.ToString(); // 6 + } + + static void M5(C? c, object? obj) + { + _ = c?.M0(out obj) is bool and true + ? obj.ToString() + : obj.ToString(); // 7 + } + + static void M6(C? c, object? obj) + { + _ = c?.M0(out obj) is { } and true + ? obj.ToString() + : obj.ToString(); // 8 + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (10,40): error CS0029: Cannot implicitly convert type 'int' to 'bool' + // _ = c?.M0(out obj) is true and 1 // 1 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", "bool").WithLocation(10, 40), + // (11,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(11, 15), + // (12,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(12, 15), + // (19,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(19, 15), + // (26,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(26, 15), + // (33,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(33, 15), + // (40,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(40, 15), + // (47,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 8 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(47, 15) + ); + } + + [Theory] + [InlineData("[NotNullWhen(true)] out object? obj")] + [InlineData("[MaybeNullWhen(false)] out object obj")] + public void IsCondAccess_NotNullWhenTrue_03(string param) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class C +{ + bool M0(" + param + @") { obj = new object(); return true; } + + static void M1(C? c, object? obj) + { + _ = c?.M0(out obj) is true or 1 // 1 + ? obj.ToString() // 2 + : obj.ToString(); // 3 + } + + static void M2(C? c, object? obj) + { + _ = c?.M0(out obj) is true or var x // 4, 5 + ? obj.ToString() // 6 + : obj.ToString(); // unreachable + } + + static void M3(C? c, object? obj) + { + _ = c?.M0(out obj) is _ or true // 7 + ? obj.ToString() // 8 + : obj.ToString(); // unreachable + } + + static void M4(C? c, object? obj) + { + _ = c?.M0(out obj) is true or bool b // 9 + ? obj.ToString() // 10 + : obj.ToString(); // 11 + } + + static void M5(C? c, object? obj) + { + _ = c?.M0(out obj) is bool or true + ? obj.ToString() // 12 + : obj.ToString(); // 13 + } + + static void M6(C? c, object? obj) + { + _ = c?.M0(out obj) is { } or true + ? obj.ToString() // 14 + : obj.ToString(); // 15 + } + + static void M7(C? c, object? obj) + { + _ = c?.M0(out obj) is true or false + ? obj.ToString() // 16 + : obj.ToString(); // 17 + } + + static void M8(C? c, object? obj) + { + _ = c?.M0(out obj) is null + ? obj.ToString() // 18 + : obj.ToString(); // 19 + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (10,39): error CS0029: Cannot implicitly convert type 'int' to 'bool?' + // _ = c?.M0(out obj) is true or 1 // 1 + Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", "bool?").WithLocation(10, 39), + // (11,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(11, 15), + // (12,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(12, 15), + // (17,13): warning CS8794: An expression of type 'bool?' always matches the provided pattern. + // _ = c?.M0(out obj) is true or var x // 4, 5 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "c?.M0(out obj) is true or var x").WithArguments("bool?").WithLocation(17, 13), + // (17,43): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // _ = c?.M0(out obj) is true or var x // 4, 5 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "x").WithLocation(17, 43), + // (18,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(18, 15), + // (24,13): warning CS8794: An expression of type 'bool?' always matches the provided pattern. + // _ = c?.M0(out obj) is _ or true // 7 + Diagnostic(ErrorCode.WRN_IsPatternAlways, "c?.M0(out obj) is _ or true").WithArguments("bool?").WithLocation(24, 13), + // (25,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 8 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(25, 15), + // (31,44): error CS8780: A variable may not be declared within a 'not' or 'or' pattern. + // _ = c?.M0(out obj) is true or bool b // 9 + Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "b").WithLocation(31, 44), + // (32,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 10 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(32, 15), + // (33,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 11 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(33, 15), + // (39,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 12 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(39, 15), + // (40,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 13 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(40, 15), + // (46,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 14 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(46, 15), + // (47,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 15 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(47, 15), + // (53,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 16 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(53, 15), + // (54,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 17 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(54, 15), + // (60,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 18 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(60, 15), + // (61,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 19 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(61, 15) + ); + } + + [Theory] + [InlineData("[NotNullWhen(false)] out object? obj")] + [InlineData("[MaybeNullWhen(true)] out object obj")] + public void IsCondAccess_NotNullWhenFalse(string param) + { + var source = @" +using System.Diagnostics.CodeAnalysis; + +class C +{ + bool M0(" + param + @") { obj = new object(); return true; } + + static void M1(C? c, object? obj) + { + _ = c?.M0(out obj) is true + ? obj.ToString() // 1 + : obj.ToString(); // 2 + } + + static void M2(C? c, object? obj) + { + _ = c?.M0(out obj) is false + ? obj.ToString() + : obj.ToString(); // 3 + } + + static void M3(C? c, object? obj) + { + _ = c?.M0(out obj) is not true // `is null or false` + ? obj.ToString() // 4 + : obj.ToString(); // 5 + } + + static void M4(C? c, object? obj) + { + _ = c?.M0(out obj) is not false // `is null or true` + ? obj.ToString() // 6 + : obj.ToString(); + } + + static void M5(C? c, object? obj) + { + _ = c?.M0(out obj) is not (true or null) // `is false` + ? obj.ToString() + : obj.ToString(); // 7 + } + + static void M6(C? c, object? obj) + { + _ = c?.M0(out obj) is not (false or null) // `is true` + ? obj.ToString() // 8 + : obj.ToString(); // 9 + } +} +"; + var comp = CreateNullableCompilation(new[] { source, NotNullWhenAttributeDefinition, MaybeNullWhenAttributeDefinition }); + comp.VerifyDiagnostics( + // (11,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(11, 15), + // (12,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(12, 15), + // (19,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(19, 15), + // (25,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(25, 15), + // (26,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(26, 15), + // (32,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 6 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(32, 15), + // (40,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 7 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(40, 15), + // (46,15): warning CS8602: Dereference of a possibly null reference. + // ? obj.ToString() // 8 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(46, 15), + // (47,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 9 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(47, 15) + ); + } + + [Fact] + public void IsPattern_LeftConditionalState() + { + var source = @" +class C +{ + void M(object? obj) + { + _ = (obj != null) is true + ? obj.ToString() + : obj.ToString(); // 1 + } +}"; + var comp = CreateNullableCompilation(source); + comp.VerifyDiagnostics( + // (8,15): warning CS8602: Dereference of a possibly null reference. + // : obj.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "obj").WithLocation(8, 15)); + } + + [Fact, WorkItem(53308, "https://github.com/dotnet/roslyn/issues/53308")] + public void IsPattern_LeftCondAccess_PropertyPattern_01() + { + var source = @" +class Program +{ + void M1(B? b) + { + if (b is { C: { Prop: { } } }) + { + b.C.Prop.ToString(); + } + } + + void M2(B? b) + { + if (b?.C is { Prop: { } }) + { + b.C.Prop.ToString(); // 1 + } + } +} + +class B +{ + public C? C { get; set; } +} + +class C +{ + public string? Prop { get; set; } +}"; + // Ideally we would not issue diagnostic (1). + // However, additional work is needed in nullable pattern analysis to make this work. + var comp = CreateNullableCompilation(source); + comp.VerifyDiagnostics( + // (16,13): warning CS8602: Dereference of a possibly null reference. + // b.C.Prop.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "b.C.Prop").WithLocation(16, 13)); + } + /// Ported from . [Fact] public void EqualsCondAccess_LeftCondAccess()