From fa4e1981b6deab8f2467d4842aa9b5b12cd497a9 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 20 Apr 2021 11:58:58 -0700 Subject: [PATCH 1/7] Improved nullable '??' analysis --- .../Portable/FlowAnalysis/AbstractFlowPass.cs | 5 +- .../Portable/FlowAnalysis/NullableWalker.cs | 220 ++- .../Semantics/NullableReferenceTypesTests.cs | 1299 +++++++++++++++++ 3 files changed, 1493 insertions(+), 31 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index 5d23b6af38b52..83ec1979ada1b 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -255,7 +255,7 @@ protected string DumpLabels() } #endif - private void EnterRegionIfNeeded(BoundNode node) + protected void EnterRegionIfNeeded(BoundNode node) { if (TrackingRegions && node == this.firstInRegion && this.regionPlace == RegionPlace.Before) { @@ -272,7 +272,7 @@ protected virtual void EnterRegion() this.regionPlace = RegionPlace.Inside; } - private void LeaveRegionIfNeeded(BoundNode node) + protected void LeaveRegionIfNeeded(BoundNode node) { if (TrackingRegions && node == this.lastInRegion && this.regionPlace == RegionPlace.Inside) { @@ -2783,7 +2783,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..626dd5e8ccb10 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,23 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly BoundExpression leftOperand = node.LeftOperand; BoundExpression rightOperand = node.RightOperand; - TypeWithState leftResult = VisitRvalueWithState(leftOperand); - TypeWithState rightResult; - + // PROTOTYPE(improved-definite-assignment): + // Definite assignment has another special case for when the LHS is a non-null constant (a literal string for example). + // Should we replicate that here? 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 +4184,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,12 +4265,112 @@ 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' + private bool TryVisitConditionalAccess(BoundExpression node, out PossiblyConditionalState stateWhenNotNull) + { + var (conversion, access) = node switch + { + BoundConditionalAccess ca => (null, ca), + BoundConversion { Conversion: Conversion innerConversion, Operand: BoundConditionalAccess ca } conv when isAcceptableConversion(ca, innerConversion) => (conv, ca), + _ => default + }; + + if (access is object) + { + EnterRegionIfNeeded(access); + Unsplit(); + VisitConditionalAccess(access, out stateWhenNotNull); + if (conversion is object) + { + var operandType = ResultType; + TypeWithAnnotations explicitType = conversion.ConversionGroupOpt?.ExplicitType ?? default; + bool fromExplicitCast = explicitType.HasType; + TypeWithAnnotations targetType = fromExplicitCast ? explicitType : TypeWithAnnotations.Create(conversion.Type); + Debug.Assert(targetType.HasType); + var result = VisitConversion(conversion, + access, + conversion.Conversion, + targetType, + operandType, + checkConversion: true, + fromExplicitCast, + useLegacyWarnings: true, + assignmentKind: AssignmentKind.Assignment); + SetResultType(conversion, result); + } + Debug.Assert(!IsConditionalState); + LeaveRegionIfNeeded(access); + return true; + } + + stateWhenNotNull = default; + return false; + + // "State when not null" cannot propagate out of a conditional access if its conversion can return null when the input is non-null. + 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); + + // if input is not allowed to be null, then assume "state when not null" can propagate out + var param = method.Parameters[0]; + var paramAnnotations = GetParameterAnnotations(param); + var paramType = ApplyLValueAnnotations(param.TypeWithAnnotations, paramAnnotations); + if ((paramAnnotations & FlowAnalysisAnnotations.DisallowNull) != 0 + || paramType.ToTypeWithState().IsNotNull) + { + return true; + } + + if (method.ReturnNotNullIfParameterNotNull.Contains(param.Name)) + { + return true; + } + + // if the return is allowed to be null for a non-null input, we can't propagate out the "state when not null". + var returnState = ApplyUnconditionalAnnotations(method.ReturnTypeWithAnnotations.ToTypeWithState(), method.ReturnTypeFlowAnalysisAnnotations); + return returnState.IsNotNull; + } + } + + 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; @@ -4279,18 +4385,60 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly // 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 _); + makeAndAdjustReceiverSlot(receiver); + } - _lastConditionalAccessSlot = nextConditionalAccessSlot; + // 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); + + // The receiverState 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 receiverState, ref State); + + expr = innerCondAccess.AccessExpression; } - var accessTypeWithAnnotations = VisitLvalueWithAnnotations(node.AccessExpression); - TypeSymbol accessType = accessTypeWithAnnotations.Type; + 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 receiverState); + var accessTypeWithAnnotations = LvalueResultType; + TypeSymbol accessType = accessTypeWithAnnotations.Type; var oldType = node.Type; var resultType = oldType.IsVoidType() || oldType.IsErrorType() ? oldType : @@ -4302,6 +4450,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/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index d2361595a7ab4..6660b2966c117 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -46832,6 +46832,1305 @@ 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_RightSideBoolConstant(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; } +} +"; + // PROTOTYPE(improved-definite-assignment): Diagnostic 2 should not actually be given. + // It looks like it is necessary to dig through multiple nested BoundConversions in some cases. + 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(); + } + + 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_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(); + } + + C M1(object obj) { return this; } + B M2(object obj) { return new B(); } +} +"; + CreateNullableCompilation(new[] { source, NotNullIfNotNullAttributeDefinition }).VerifyDiagnostics(); + } + + ///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(); + } + + C M1(object obj) { return this; } + B M2(object obj) { return new B(); } +} +"; + CreateNullableCompilation(new[] { source, NotNullAttributeDefinition }).VerifyDiagnostics(); + } + + ///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(); + } + + 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)); + } + + ///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; } +} +"; + // PROTOTYPE(improved-definite-assignment): Diagnostic 2 should not actually be given. + // It looks like it is necessary to dig through multiple nested BoundConversions in some cases. + CreateNullableCompilation(new[] { source, DisallowNullAttributeDefinition }).VerifyDiagnostics( + // (13,19): warning CS8604: Possible null reference argument for parameter 'c' in 'B.explicit 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(); + } + [Fact] public void ConditionalBranching_Is_ReferenceType() { From 1e6b5c3f5803b6d05168cd656fc21cea64805031 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 26 Apr 2021 16:53:14 -0700 Subject: [PATCH 2/7] Address some feedback --- .../Portable/FlowAnalysis/NullableWalker.cs | 92 ++++++++++--------- .../Semantics/NullableReferenceTypesTests.cs | 34 +++---- 2 files changed, 65 insertions(+), 61 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 626dd5e8ccb10..b0ad33b03f089 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4268,47 +4268,45 @@ 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. + /// private bool TryVisitConditionalAccess(BoundExpression node, out PossiblyConditionalState stateWhenNotNull) { - var (conversion, access) = node switch + var (operand, conversion) = RemoveConversion(node, includeExplicitConversions: true); + if (operand is not BoundConditionalAccess access || !isAcceptableConversion(access, conversion)) { - BoundConditionalAccess ca => (null, ca), - BoundConversion { Conversion: Conversion innerConversion, Operand: BoundConditionalAccess ca } conv when isAcceptableConversion(ca, innerConversion) => (conv, ca), - _ => default - }; - - if (access is object) - { - EnterRegionIfNeeded(access); - Unsplit(); - VisitConditionalAccess(access, out stateWhenNotNull); - if (conversion is object) - { - var operandType = ResultType; - TypeWithAnnotations explicitType = conversion.ConversionGroupOpt?.ExplicitType ?? default; - bool fromExplicitCast = explicitType.HasType; - TypeWithAnnotations targetType = fromExplicitCast ? explicitType : TypeWithAnnotations.Create(conversion.Type); - Debug.Assert(targetType.HasType); - var result = VisitConversion(conversion, - access, - conversion.Conversion, - targetType, - operandType, - checkConversion: true, - fromExplicitCast, - useLegacyWarnings: true, - assignmentKind: AssignmentKind.Assignment); - SetResultType(conversion, result); - } - Debug.Assert(!IsConditionalState); - LeaveRegionIfNeeded(access); - return true; + stateWhenNotNull = default; + return false; } - stateWhenNotNull = default; - return false; + EnterRegionIfNeeded(access); + 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); + LeaveRegionIfNeeded(access); + return true; - // "State when not null" cannot propagate out of a conditional access if its conversion can return null when the input is non-null. + // "State when not null" cannot propagate out of a conditional access if its conversion can return non-null when the input is null. bool isAcceptableConversion(BoundConditionalAccess operand, Conversion conversion) { if (conversion.Kind is not (ConversionKind.ImplicitUserDefined or ConversionKind.ExplicitUserDefined)) @@ -4324,23 +4322,33 @@ bool isAcceptableConversion(BoundConditionalAccess operand, Conversion conversio var param = method.Parameters[0]; var paramAnnotations = GetParameterAnnotations(param); var paramType = ApplyLValueAnnotations(param.TypeWithAnnotations, paramAnnotations); - if ((paramAnnotations & FlowAnalysisAnnotations.DisallowNull) != 0 - || paramType.ToTypeWithState().IsNotNull) + var paramState = paramType.ToTypeWithState(); + if (paramState.IsNotNull + || (paramAnnotations & FlowAnalysisAnnotations.DisallowNull) != 0) { return true; } - if (method.ReturnNotNullIfParameterNotNull.Contains(param.Name)) + // From here on we know the input is allowed to be `null`. + var returnState = ApplyUnconditionalAnnotations(method.ReturnTypeWithAnnotations.ToTypeWithState(), method.ReturnTypeFlowAnalysisAnnotations); + if (returnState.IsNotNull) { - return true; + // We can't learn from this conversion because the result will be not-null even if the input was `null`. + return false; } - // if the return is allowed to be null for a non-null input, we can't propagate out the "state when not null". - var returnState = ApplyUnconditionalAnnotations(method.ReturnTypeWithAnnotations.ToTypeWithState(), method.ReturnTypeFlowAnalysisAnnotations); - return returnState.IsNotNull; + // Here the parameter may be null, and the return also may be null. + // We will only learn from this conversion if it has `[return: NotNullIfNotNull]`, + // because we will assume that the conversion always returns a `null` result for a `null` input, + // and always returns a non-null result for a non-null input. + Debug.Assert(returnState.MayBeNull); + return method.ReturnNotNullIfParameterNotNull.Contains(param.Name); } } + /// + /// 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)) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 6660b2966c117..db7c7b0c86746 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -46929,7 +46929,7 @@ static void M6(C? c) [Theory] [InlineData("[NotNullWhen(true)] out object? obj")] [InlineData("[MaybeNullWhen(false)] out object obj")] - public void NotNullWhenTrue_NullCoalescing_RightSideBoolConstant(string param) + public void NotNullWhenTrue_NullCoalescing_RightSideBool(string param) { var source = @" using System.Diagnostics.CodeAnalysis; @@ -47835,22 +47835,17 @@ struct C void M1(C? c1, object? x) { S s = (S?)c1?.M2(x = 0) ?? c1!.Value.M3(x = 0); // 1 - x.ToString(); // 2 + x.ToString(); } C M2(object obj) { return this; } S M3(object obj) { return (S)this; } } "; - // PROTOTYPE(improved-definite-assignment): Diagnostic 2 should not actually be given. - // It looks like it is necessary to dig through multiple nested BoundConversions in some cases. 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) + Diagnostic(ErrorCode.WRN_DisallowNullAttributeForbidsMaybeNullAssignment, "c1?.M2(x = 0)").WithLocation(15, 19) ); } @@ -47869,14 +47864,17 @@ class C void M1(C c1, object? x) { B b = (B)c1?.M1(x = 0) ?? c1!.M2(x = 0); - x.ToString(); + x.ToString(); // 1 } C M1(object obj) { return this; } B M2(object obj) { return new B(); } } "; - CreateNullableCompilation(source).VerifyDiagnostics(); + 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 . @@ -47953,14 +47951,17 @@ class C void M1(C c1, object? x) { B b = (B?)c1?.M1(x = 0) ?? c1!.M2(x = 0); - x.ToString(); + x.ToString(); // 1 } C M1(object obj) { return this; } B M2(object obj) { return new B(); } } "; - CreateNullableCompilation(new[] { source, NotNullAttributeDefinition }).VerifyDiagnostics(); + 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 . @@ -48091,22 +48092,17 @@ class C static void M1(C c1, object? x) { B b = (B?)c1?.M1(x = 0) ?? c1!.M2(x = 0); // 1 - x.ToString(); // 2 + x.ToString(); } C M1(object obj) { return this; } B M2(object obj) { return default; } } "; - // PROTOTYPE(improved-definite-assignment): Diagnostic 2 should not actually be given. - // It looks like it is necessary to dig through multiple nested BoundConversions in some cases. CreateNullableCompilation(new[] { source, DisallowNullAttributeDefinition }).VerifyDiagnostics( // (13,19): warning CS8604: Possible null reference argument for parameter 'c' in 'B.explicit 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) + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "c1?.M1(x = 0)").WithArguments("c", "B." + conversionKind + " operator B(C? c)").WithLocation(13, 19) ); } From df3ba7495af5d9bfadb86cc859c66d9d6aca7885 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 26 Apr 2021 17:54:53 -0700 Subject: [PATCH 3/7] Handle non-null constants on left of ?. --- .../Portable/FlowAnalysis/AbstractFlowPass.cs | 15 ++- .../Portable/FlowAnalysis/NullableWalker.cs | 107 ++++++++++-------- .../Test/Semantic/FlowAnalysis/FlowTests.cs | 34 ++++++ .../Semantics/NullableReferenceTypesTests.cs | 30 +++++ 4 files changed, 136 insertions(+), 50 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index 83ec1979ada1b..01375dd4617de 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -2760,8 +2760,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 { diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index b0ad33b03f089..b9d1c216a8313 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4382,68 +4382,79 @@ private void VisitConditionalAccess(BoundConditionalAccess node, out PossiblyCon _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); - makeAndAdjustReceiverSlot(receiver); - } + var receiverState = 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 receiverState); + 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); + // 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); - // The receiverState 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 receiverState, ref State); + // The receiverState 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 receiverState, ref State); - expr = innerCondAccess.AccessExpression; - } + expr = innerCondAccess.AccessExpression; + } - Debug.Assert(expr is BoundExpression); - Visit(expr); + 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) + expr = node.AccessExpression; + while (expr is BoundConditionalAccess innerCondAccess) { - LearnFromNonNullTest(slot, ref StateWhenTrue); - LearnFromNonNullTest(slot, ref StateWhenFalse); + // 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; } - else + Debug.Assert(expr is BoundExpression); + var slot = MakeSlot(expr); + if (slot > -1) { - LearnFromNonNullTest(slot, ref State); + 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 receiverState); + stateWhenNotNull = PossiblyConditionalState.Create(this); + Unsplit(); + Join(ref this.State, ref receiverState); + } var accessTypeWithAnnotations = LvalueResultType; TypeSymbol accessType = accessTypeWithAnnotations.Type; diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs index 591e04adcc780..b9a0b67fd9d75 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs @@ -2385,6 +2385,40 @@ 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_ConditionalLeft() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index db7c7b0c86746..544e912f901b6 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -48127,6 +48127,36 @@ static void M2(E? e, object? x) 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)); + } + [Fact] public void ConditionalBranching_Is_ReferenceType() { From c5b9624bc92600dfe607088288a09185c34c94ee Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 26 Apr 2021 17:58:44 -0700 Subject: [PATCH 4/7] Address feedback --- .../CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs | 4 ++-- .../CSharp/Portable/FlowAnalysis/NullableWalker.cs | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index 01375dd4617de..eddeb15aeb49d 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs @@ -255,7 +255,7 @@ protected string DumpLabels() } #endif - protected void EnterRegionIfNeeded(BoundNode node) + private void EnterRegionIfNeeded(BoundNode node) { if (TrackingRegions && node == this.firstInRegion && this.regionPlace == RegionPlace.Before) { @@ -272,7 +272,7 @@ protected virtual void EnterRegion() this.regionPlace = RegionPlace.Inside; } - protected void LeaveRegionIfNeeded(BoundNode node) + private void LeaveRegionIfNeeded(BoundNode node) { if (TrackingRegions && node == this.lastInRegion && this.regionPlace == RegionPlace.Inside) { diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index b9d1c216a8313..547a0e33d2f63 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4281,7 +4281,6 @@ private bool TryVisitConditionalAccess(BoundExpression node, out PossiblyConditi return false; } - EnterRegionIfNeeded(access); Unsplit(); VisitConditionalAccess(access, out stateWhenNotNull); if (node is BoundConversion boundConversion) @@ -4303,7 +4302,6 @@ private bool TryVisitConditionalAccess(BoundExpression node, out PossiblyConditi SetResultType(boundConversion, result); } Debug.Assert(!IsConditionalState); - LeaveRegionIfNeeded(access); return true; // "State when not null" cannot propagate out of a conditional access if its conversion can return non-null when the input is null. @@ -4392,7 +4390,7 @@ private void VisitConditionalAccess(BoundConditionalAccess node, out PossiblyCon } else { - var receiverState = this.State.Clone(); + var savedState = this.State.Clone(); if (IsConstantNull(receiver)) { SetUnreachable(); @@ -4402,7 +4400,7 @@ private void VisitConditionalAccess(BoundConditionalAccess node, out PossiblyCon { // 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); + LearnFromNullTest(receiver, ref savedState); makeAndAdjustReceiverSlot(receiver); } @@ -4419,9 +4417,9 @@ private void VisitConditionalAccess(BoundConditionalAccess node, out PossiblyCon _currentConditionalReceiverVisitResult = _visitResult; makeAndAdjustReceiverSlot(innerCondAccess.Receiver); - // The receiverState here represents the scenario where 0 or more of the access expressions could have been evaluated. + // 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 receiverState, ref State); + Join(ref savedState, ref State); expr = innerCondAccess.AccessExpression; } @@ -4453,7 +4451,7 @@ private void VisitConditionalAccess(BoundConditionalAccess node, out PossiblyCon stateWhenNotNull = PossiblyConditionalState.Create(this); Unsplit(); - Join(ref this.State, ref receiverState); + Join(ref this.State, ref savedState); } var accessTypeWithAnnotations = LvalueResultType; From 8c6ccc6ef0becaeb59042e04fe810102bf10fab8 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 27 Apr 2021 12:23:31 -0700 Subject: [PATCH 5/7] Add tests and remove prototype comment --- .../Portable/FlowAnalysis/NullableWalker.cs | 5 +- .../Test/Semantic/FlowAnalysis/FlowTests.cs | 23 +++++++ .../Semantics/NullableReferenceTypesTests.cs | 66 +++++++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 547a0e33d2f63..2a76fbc80028d 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4159,12 +4159,9 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly BoundExpression leftOperand = node.LeftOperand; BoundExpression rightOperand = node.RightOperand; - // PROTOTYPE(improved-definite-assignment): - // Definite assignment has another special case for when the LHS is a non-null constant (a literal string for example). - // Should we replicate that here? if (IsConstantNull(leftOperand)) { - VisitRvalue(leftOperand); + SetResultType(leftOperand, TypeWithState.Create(leftOperand.Type, NullableFlowState.MaybeDefault)); Visit(rightOperand); var rightUnconditionalResult = ResultType; // Should be able to use rightResult for the result of the operator but diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs index b9a0b67fd9d75..30dde86b974e0 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/FlowTests.cs @@ -2419,6 +2419,29 @@ static void M2() ); } + [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 544e912f901b6..3391479c76de5 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -48157,6 +48157,72 @@ static void M2(object? x, object? y) 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 68eb9b1b5c74bffa6c08e2bd5bd08056fddeadf4 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 27 Apr 2021 13:27:05 -0700 Subject: [PATCH 6/7] Use VisitRvalue for null constants --- src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 2a76fbc80028d..056f824d6f011 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4161,7 +4161,7 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly if (IsConstantNull(leftOperand)) { - SetResultType(leftOperand, TypeWithState.Create(leftOperand.Type, NullableFlowState.MaybeDefault)); + VisitRvalue(leftOperand); Visit(rightOperand); var rightUnconditionalResult = ResultType; // Should be able to use rightResult for the result of the operator but From b20fa3cf91f6ebbb6a304a1f1b4d1c32cf3cf46d Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 3 May 2021 15:39:33 -0700 Subject: [PATCH 7/7] Use strict "acceptable conversion" rules in nullable --- .../Portable/FlowAnalysis/AbstractFlowPass.cs | 39 ++++++++++-------- .../Portable/FlowAnalysis/NullableWalker.cs | 41 +------------------ .../Semantics/NullableReferenceTypesTests.cs | 31 ++++++++++---- 3 files changed, 44 insertions(+), 67 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs index eddeb15aeb49d..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; } /// diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 056f824d6f011..2f02ef2aa5562 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4272,7 +4272,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 || !isAcceptableConversion(access, conversion)) + if (operand is not BoundConditionalAccess access || !CanPropagateStateWhenNotNull(access, conversion)) { stateWhenNotNull = default; return false; @@ -4300,45 +4300,6 @@ private bool TryVisitConditionalAccess(BoundExpression node, out PossiblyConditi } Debug.Assert(!IsConditionalState); return true; - - // "State when not null" cannot propagate out of a conditional access if its conversion can return non-null when the input is null. - 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); - - // if input is not allowed to be null, then assume "state when not null" can propagate out - var param = method.Parameters[0]; - var paramAnnotations = GetParameterAnnotations(param); - var paramType = ApplyLValueAnnotations(param.TypeWithAnnotations, paramAnnotations); - var paramState = paramType.ToTypeWithState(); - if (paramState.IsNotNull - || (paramAnnotations & FlowAnalysisAnnotations.DisallowNull) != 0) - { - return true; - } - - // From here on we know the input is allowed to be `null`. - var returnState = ApplyUnconditionalAnnotations(method.ReturnTypeWithAnnotations.ToTypeWithState(), method.ReturnTypeFlowAnalysisAnnotations); - if (returnState.IsNotNull) - { - // We can't learn from this conversion because the result will be not-null even if the input was `null`. - return false; - } - - // Here the parameter may be null, and the return also may be null. - // We will only learn from this conversion if it has `[return: NotNullIfNotNull]`, - // because we will assume that the conversion always returns a `null` result for a `null` input, - // and always returns a non-null result for a non-null input. - Debug.Assert(returnState.MayBeNull); - return method.ReturnNotNullIfParameterNotNull.Contains(param.Name); - } } /// diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 3391479c76de5..95eda8fc0fde7 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -47835,7 +47835,7 @@ struct C void M1(C? c1, object? x) { S s = (S?)c1?.M2(x = 0) ?? c1!.Value.M3(x = 0); // 1 - x.ToString(); + x.ToString(); // 2 } C M2(object obj) { return this; } @@ -47845,7 +47845,10 @@ void M1(C? c1, object? x) 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) + 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) ); } @@ -47923,14 +47926,17 @@ class C void M1(C c1, object? x) { B b = (B?)c1?.M1(x = 0) ?? c1!.M2(x = 0); - x.ToString(); + x.ToString(); // 1 } C M1(object obj) { return this; } B M2(object obj) { return new B(); } } "; - CreateNullableCompilation(new[] { source, NotNullIfNotNullAttributeDefinition }).VerifyDiagnostics(); + 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 . @@ -47979,7 +47985,7 @@ class C void M1(C c1, object? x) { B b = (B?)c1?.M1(x = 0) ?? c1!.M2(x = 0); // 1 - x.ToString(); + x.ToString(); // 2 } C M1(object obj) { return this; } @@ -47989,7 +47995,11 @@ void M1(C c1, object? x) 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)); + 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 . @@ -48092,7 +48102,7 @@ class C static void M1(C c1, object? x) { B b = (B?)c1?.M1(x = 0) ?? c1!.M2(x = 0); // 1 - x.ToString(); + x.ToString(); // 2 } C M1(object obj) { return this; } @@ -48100,9 +48110,12 @@ static void M1(C c1, object? x) } "; CreateNullableCompilation(new[] { source, DisallowNullAttributeDefinition }).VerifyDiagnostics( - // (13,19): warning CS8604: Possible null reference argument for parameter 'c' in 'B.explicit operator B(C? c)'. + // (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) + 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) ); }