From fa4e1981b6deab8f2467d4842aa9b5b12cd497a9 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 20 Apr 2021 11:58:58 -0700 Subject: [PATCH] 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() {