From a3e9ea80251cbd5252902dfbae214996ba26232b Mon Sep 17 00:00:00 2001 From: Fredric Silberberg Date: Fri, 19 Mar 2021 18:48:18 -0700 Subject: [PATCH 1/3] Correctly handle conversions in null coalescing We weren't correctly using the results of the left conversion for cases when the rhs of a null-coalescing assignment determines the output type and was a user-defined conversion. I've also added tests for #29955, which I don't believe needs to be addressed as I couldn't find any scenario that was broken due to it. Fixes https://github.com/dotnet/roslyn/issues/51975. Fixes https://github.com/dotnet/roslyn/issues/29955. --- .../Semantics/Conversions/Conversion.cs | 2 + .../Portable/FlowAnalysis/NullableWalker.cs | 55 ++-- .../Semantics/NullableReferenceTypesTests.cs | 234 ++++++++++++++++++ 3 files changed, 271 insertions(+), 20 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs index 7d0167a039d28..2971a21fff8d0 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs @@ -9,6 +9,7 @@ using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.Operations; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.CodeAnalysis.CSharp { @@ -634,6 +635,7 @@ public bool IsReference /// /// Implicit and explicit user-defined conversions are described in section 6.4 of the C# language specification. /// + [MemberNotNullWhen(true, nameof(Method), nameof(MethodSymbol))] public bool IsUserDefined { get diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 9cdf225c61d39..0ba57601dba63 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4146,7 +4146,7 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly TypeWithState rightResult = VisitOptionalImplicitConversion(rightOperand, targetType, useLegacyWarnings: UseLegacyWarnings(leftOperand, targetType), trackMembers: false, AssignmentKind.Assignment); TrackNullableStateForAssignment(rightOperand, targetType, leftSlot, rightResult, MakeSlot(rightOperand)); Join(ref this.State, ref leftState); - TypeWithState resultType = GetNullCoalescingResultType(rightResult, targetType.Type); + TypeWithState resultType = GetNullCoalescingResultType(rightResult, TypeWithState.Create(targetType.Type, NullableFlowState.NotNull)); SetResultType(node, resultType); return null; } @@ -4180,8 +4180,6 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly SetUnreachable(); } - // https://github.com/dotnet/roslyn/issues/29955 For cases where the left operand determines - // the type, we should unwrap the right conversion and re-apply. rightResult = VisitRvalueWithState(rightOperand); Join(ref this.State, ref whenNotNull); @@ -4202,47 +4200,64 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly var leftResultType = leftResult.Type; var rightResultType = rightResult.Type; - var resultType = node.OperatorResultKind switch + var resultTypeWithLeftState = node.OperatorResultKind switch { - BoundNullCoalescingOperatorResultKind.NoCommonType => node.Type, + BoundNullCoalescingOperatorResultKind.NoCommonType => TypeWithState.Create(node.Type, NullableFlowState.NotNull), BoundNullCoalescingOperatorResultKind.LeftType => getLeftResultType(leftResultType!, rightResultType!), BoundNullCoalescingOperatorResultKind.LeftUnwrappedType => getLeftResultType(leftResultType!.StrippedType(), rightResultType!), - BoundNullCoalescingOperatorResultKind.RightType => getRightResultType(leftResultType!, rightResultType!), - BoundNullCoalescingOperatorResultKind.LeftUnwrappedRightType => getRightResultType(leftResultType!.StrippedType(), rightResultType!), - BoundNullCoalescingOperatorResultKind.RightDynamicType => rightResultType!, + BoundNullCoalescingOperatorResultKind.RightType => getRightResultType(leftResult, leftResultType!, rightResultType!), + BoundNullCoalescingOperatorResultKind.LeftUnwrappedRightType => getRightResultType(leftResult, leftResultType!.StrippedType(), rightResultType!), + BoundNullCoalescingOperatorResultKind.RightDynamicType => TypeWithState.Create(rightResultType!, NullableFlowState.NotNull), _ => throw ExceptionUtilities.UnexpectedValue(node.OperatorResultKind), }; - SetResultType(node, GetNullCoalescingResultType(rightResult, resultType)); + SetResultType(node, GetNullCoalescingResultType(rightResult, resultTypeWithLeftState)); return null; - TypeSymbol getLeftResultType(TypeSymbol leftType, TypeSymbol rightType) + TypeWithState getLeftResultType(TypeSymbol leftType, TypeSymbol rightType) { Debug.Assert(rightType is object); // If there was an identity conversion between the two operands (in short, if there // is no implicit conversion on the right operand), then check nullable conversions // in both directions since it's possible the right operand is the better result type. if ((node.RightOperand as BoundConversion)?.ExplicitCastInCode != false && - GenerateConversionForConditionalOperator(node.LeftOperand, leftType, rightType, reportMismatch: false).Exists) + GenerateConversionForConditionalOperator(node.LeftOperand, leftType, rightType, reportMismatch: false) is { Exists: true } conversion) { - return rightType; + Debug.Assert(!conversion.IsUserDefined); + return TypeWithState.Create(rightType, NullableFlowState.NotNull); } - GenerateConversionForConditionalOperator(node.RightOperand, rightType, leftType, reportMismatch: true); - return leftType; + conversion = GenerateConversionForConditionalOperator(node.RightOperand, rightType, leftType, reportMismatch: true); + Debug.Assert(!conversion.IsUserDefined); + return TypeWithState.Create(leftType, NullableFlowState.NotNull); } - TypeSymbol getRightResultType(TypeSymbol leftType, TypeSymbol rightType) + TypeWithState getRightResultType(TypeWithState leftTypeWithState, TypeSymbol leftType, TypeSymbol rightType) { - GenerateConversionForConditionalOperator(node.LeftOperand, leftType, rightType, reportMismatch: true); - return rightType; + var conversion = GenerateConversionForConditionalOperator(node.LeftOperand, leftType, rightType, reportMismatch: true); + if (conversion.IsUserDefined) + { + return VisitConversion( + conversionOpt: null, + node.LeftOperand, + conversion, + TypeWithAnnotations.Create(rightType), + leftTypeWithState, + checkConversion: false, + fromExplicitCast: false, + useLegacyWarnings: false, + AssignmentKind.Assignment, + reportTopLevelWarnings: false, + reportRemainingWarnings: false); + } + + return TypeWithState.Create(rightType, NullableFlowState.NotNull); } } - private static TypeWithState GetNullCoalescingResultType(TypeWithState rightResult, TypeSymbol resultType) + private static TypeWithState GetNullCoalescingResultType(TypeWithState rightResult, TypeWithState resultTypeWithLeftState) { - NullableFlowState resultState = rightResult.State; - return TypeWithState.Create(resultType, resultState); + return TypeWithState.Create(resultTypeWithLeftState.Type, rightResult.State.Join(resultTypeWithLeftState.State)); } public override BoundNode? VisitConditionalAccess(BoundConditionalAccess node) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 21b9947a7ac06..d2361595a7ab4 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -49924,6 +49924,240 @@ static object F((I, I)? x, (I, I) y) Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "y").WithArguments("(I, I)", "(I, I)").WithLocation(10, 21)); } + [Fact, WorkItem(51975, "https://github.com/dotnet/roslyn/issues/51975")] + public void NullCoalescingOperator_09() + { + var source = +@"C? c = new C(); +D d = c ?? new D(); + +d.ToString(); + +class C {} + +class D +{ + public static implicit operator D?(C c) => default; +} +"; + + var comp = CreateCompilation(source, options: WithNullableEnable(TestOptions.ReleaseExe)); + comp.VerifyDiagnostics( + // (2,7): warning CS8600: Converting null literal or possible null value to non-nullable type. + // D d = c ?? new D(); + Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "c ?? new D()").WithLocation(2, 7), + // (4,1): warning CS8602: Dereference of a possibly null reference. + // d.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "d").WithLocation(4, 1) + ); + } + + [Fact, WorkItem(51975, "https://github.com/dotnet/roslyn/issues/51975")] + public void NullCoalescingOperator_10() + { + var source = +@"C? c = new C(); +D d = c ?? new D(); + +d.ToString(); + +class C +{ + public static implicit operator D?(C c) => default; +} + +class D {} +"; + + var comp = CreateCompilation(source, options: WithNullableEnable(TestOptions.ReleaseExe)); + comp.VerifyDiagnostics( + // (2,7): warning CS8600: Converting null literal or possible null value to non-nullable type. + // D d = c ?? new D(); + Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "c ?? new D()").WithLocation(2, 7), + // (4,1): warning CS8602: Dereference of a possibly null reference. + // d.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "d").WithLocation(4, 1) + ); + } + + [Fact, WorkItem(51975, "https://github.com/dotnet/roslyn/issues/51975")] + public void NullCoalescingOperator_11() + { + var source = +@"C? c = new C(); +D d = c ?? new D(); + +d.ToString(); + +struct C +{ + public static implicit operator D?(C c) => default; +} + +class D {} +"; + + var comp = CreateCompilation(source, options: WithNullableEnable(TestOptions.ReleaseExe)); + comp.VerifyDiagnostics( + // (2,7): warning CS8600: Converting null literal or possible null value to non-nullable type. + // D d = c ?? new D(); + Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "c ?? new D()").WithLocation(2, 7), + // (4,1): warning CS8602: Dereference of a possibly null reference. + // d.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "d").WithLocation(4, 1) + ); + } + + [Fact, WorkItem(51975, "https://github.com/dotnet/roslyn/issues/51975")] + public void NullCoalescingOperator_12() + { + var source = +@"C? c = new C(); +C c2 = c ?? new D(); + +c2.ToString(); + +class C +{ + public static implicit operator C?(D c) => default; +} + +class D {} +"; + + var comp = CreateCompilation(source, options: WithNullableEnable(TestOptions.ReleaseExe)); + comp.VerifyDiagnostics( + // (2,8): warning CS8600: Converting null literal or possible null value to non-nullable type. + // C c2 = c ?? new D(); + Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "c ?? new D()").WithLocation(2, 8), + // (4,1): warning CS8602: Dereference of a possibly null reference. + // c2.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c2").WithLocation(4, 1) + ); + } + + [Fact, WorkItem(29955, "https://github.com/dotnet/roslyn/issues/29955")] + public void NullCoalescingOperator_13() + { + var source = +@"C c = new C(); +C c2 = c ?? new D(); + +c2.ToString(); + +class C +{ + public static implicit operator C(D c) => default!; +} + +class D {} +"; + + var comp = CreateCompilation(source, options: WithNullableEnable(TestOptions.ReleaseExe)); + comp.VerifyDiagnostics( + // (2,22): warning CS8619: Nullability of reference types in value of type 'D' doesn't match target type 'D'. + // C c2 = c ?? new D(); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "new D()").WithArguments("D", "D").WithLocation(2, 22) + ); + } + + [Fact, WorkItem(29955, "https://github.com/dotnet/roslyn/issues/29955")] + public void NullCoalescingOperator_14() + { + var source = +@"using System; +Action a1 = param => {}; +Action a2 = a1 ?? ((string s) => {}); +"; + + var comp = CreateCompilation(source, options: WithNullableEnable(TestOptions.ReleaseExe)); + comp.VerifyDiagnostics( + // (3,29): warning CS8622: Nullability of reference types in type of parameter 's' of 'lambda expression' doesn't match the target delegate 'Action' (possibly because of nullability attributes). + // Action a2 = a1 ?? ((string s) => {}); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, "(string s) => {}").WithArguments("s", "lambda expression", "System.Action").WithLocation(3, 29) + ); + } + + [Fact, WorkItem(29955, "https://github.com/dotnet/roslyn/issues/29955")] + public void NullCoalescingOperator_15() + { + var source = +@"using System; +Action a1 = param => {}; +Action a2 = a1 ?? (Action)((string s) => {}); +"; + + var comp = CreateCompilation(source, options: WithNullableEnable(TestOptions.ReleaseExe)); + comp.VerifyDiagnostics( + // (3,22): warning CS8619: Nullability of reference types in value of type 'Action' doesn't match target type 'Action'. + // Action a2 = a1 ?? (Action)((string s) => {}); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "a1 ?? (Action)((string s) => {})").WithArguments("System.Action", "System.Action").WithLocation(3, 22) + ); + } + + [Fact, WorkItem(29955, "https://github.com/dotnet/roslyn/issues/29955")] + public void NullCoalescingOperator_16() + { + var source = +@"using System; +Func a1 = () => string.Empty; +Func a2 = a1 ?? (() => null); +"; + + var comp = CreateCompilation(source, options: WithNullableEnable(TestOptions.ReleaseExe)); + comp.VerifyDiagnostics( + // (3,32): warning CS8603: Possible null reference return. + // Func a2 = a1 ?? (() => null); + Diagnostic(ErrorCode.WRN_NullReferenceReturn, "null").WithLocation(3, 32) + ); + } + + [Fact, WorkItem(29955, "https://github.com/dotnet/roslyn/issues/29955")] + public void NullCoalescingOperator_17() + { + var source = +@"using System; +Func a1 = () => string.Empty; +Func a2 = a1 ?? (Func)(() => null); +"; + + var comp = CreateCompilation(source, options: WithNullableEnable(TestOptions.ReleaseExe)); + comp.VerifyDiagnostics( + // (3,19): warning CS8619: Nullability of reference types in value of type 'Func' doesn't match target type 'Func'. + // Func a2 = a1 ?? (Func)(() => null); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "a1 ?? (Func)(() => null)").WithArguments("System.Func", "System.Func").WithLocation(3, 19) + ); + } + + [Fact, WorkItem(51975, "https://github.com/dotnet/roslyn/issues/51975")] + public void NullCoalescingOperator_18() + { + var source = +@"using System.Diagnostics.CodeAnalysis; +C? c = new C(); +D d1 = c ?? new D(); +d1.ToString(); + +D d2 = ((C?)null) ?? new D(); +d2.ToString(); + +c = null; +D d3 = c ?? new D(); +d3.ToString(); + +class C {} + +class D +{ + [return: NotNullIfNotNull(""c"")] + public static implicit operator D?(C c) => default!; +} +"; + + var comp = CreateCompilation(new[] { source, NotNullIfNotNullAttributeDefinition }, options: WithNullableEnable(TestOptions.ReleaseExe)); + comp.VerifyDiagnostics(); + } + [Fact] public void IdentityConversion_NullCoalescingOperator_01() { From 8a1787c65119ded4a5564efe92ad6b7fd621f239 Mon Sep 17 00:00:00 2001 From: Fredric Silberberg Date: Fri, 19 Mar 2021 20:48:05 -0700 Subject: [PATCH 2/3] Correct conversion state handling. --- .../CSharp/Portable/FlowAnalysis/NullableWalker.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 0ba57601dba63..38685a3c626f7 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4205,8 +4205,8 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly BoundNullCoalescingOperatorResultKind.NoCommonType => TypeWithState.Create(node.Type, NullableFlowState.NotNull), BoundNullCoalescingOperatorResultKind.LeftType => getLeftResultType(leftResultType!, rightResultType!), BoundNullCoalescingOperatorResultKind.LeftUnwrappedType => getLeftResultType(leftResultType!.StrippedType(), rightResultType!), - BoundNullCoalescingOperatorResultKind.RightType => getRightResultType(leftResult, leftResultType!, rightResultType!), - BoundNullCoalescingOperatorResultKind.LeftUnwrappedRightType => getRightResultType(leftResult, leftResultType!.StrippedType(), rightResultType!), + BoundNullCoalescingOperatorResultKind.RightType => getResultStateWithRightType(leftResultType!, rightResultType!), + BoundNullCoalescingOperatorResultKind.LeftUnwrappedRightType => getResultStateWithRightType(leftResultType!.StrippedType(), rightResultType!), BoundNullCoalescingOperatorResultKind.RightDynamicType => TypeWithState.Create(rightResultType!, NullableFlowState.NotNull), _ => throw ExceptionUtilities.UnexpectedValue(node.OperatorResultKind), }; @@ -4232,7 +4232,7 @@ TypeWithState getLeftResultType(TypeSymbol leftType, TypeSymbol rightType) return TypeWithState.Create(leftType, NullableFlowState.NotNull); } - TypeWithState getRightResultType(TypeWithState leftTypeWithState, TypeSymbol leftType, TypeSymbol rightType) + TypeWithState getResultStateWithRightType(TypeSymbol leftType, TypeSymbol rightType) { var conversion = GenerateConversionForConditionalOperator(node.LeftOperand, leftType, rightType, reportMismatch: true); if (conversion.IsUserDefined) @@ -4242,7 +4242,9 @@ TypeWithState getRightResultType(TypeWithState leftTypeWithState, TypeSymbol lef node.LeftOperand, conversion, TypeWithAnnotations.Create(rightType), - leftTypeWithState, + // When considering the conversion on the left node, it can only occur in the case where the underlying + // execution returned non-null + TypeWithState.Create(leftType, NullableFlowState.NotNull), checkConversion: false, fromExplicitCast: false, useLegacyWarnings: false, From 354acb799145f8b1309cac3b15bcad982dff77b9 Mon Sep 17 00:00:00 2001 From: Fredric Silberberg Date: Fri, 26 Mar 2021 14:39:01 -0700 Subject: [PATCH 3/3] Remove incorrect MemberNotNullAttribute, simplify and inline logic. --- .../Semantics/Conversions/Conversion.cs | 1 - .../Portable/FlowAnalysis/NullableWalker.cs | 29 +++++++++---------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs index 2971a21fff8d0..b3a84335348d5 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs @@ -635,7 +635,6 @@ public bool IsReference /// /// Implicit and explicit user-defined conversions are described in section 6.4 of the C# language specification. /// - [MemberNotNullWhen(true, nameof(Method), nameof(MethodSymbol))] public bool IsUserDefined { get diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 38685a3c626f7..48e7a026c1e52 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4146,7 +4146,7 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly TypeWithState rightResult = VisitOptionalImplicitConversion(rightOperand, targetType, useLegacyWarnings: UseLegacyWarnings(leftOperand, targetType), trackMembers: false, AssignmentKind.Assignment); TrackNullableStateForAssignment(rightOperand, targetType, leftSlot, rightResult, MakeSlot(rightOperand)); Join(ref this.State, ref leftState); - TypeWithState resultType = GetNullCoalescingResultType(rightResult, TypeWithState.Create(targetType.Type, NullableFlowState.NotNull)); + TypeWithState resultType = TypeWithState.Create(targetType.Type, rightResult.State); SetResultType(node, resultType); return null; } @@ -4200,21 +4200,21 @@ private static BoundExpression SkipReferenceConversions(BoundExpression possibly var leftResultType = leftResult.Type; var rightResultType = rightResult.Type; - var resultTypeWithLeftState = node.OperatorResultKind switch + var (resultType, leftState) = node.OperatorResultKind switch { - BoundNullCoalescingOperatorResultKind.NoCommonType => TypeWithState.Create(node.Type, NullableFlowState.NotNull), + BoundNullCoalescingOperatorResultKind.NoCommonType => (node.Type, NullableFlowState.NotNull), BoundNullCoalescingOperatorResultKind.LeftType => getLeftResultType(leftResultType!, rightResultType!), BoundNullCoalescingOperatorResultKind.LeftUnwrappedType => getLeftResultType(leftResultType!.StrippedType(), rightResultType!), BoundNullCoalescingOperatorResultKind.RightType => getResultStateWithRightType(leftResultType!, rightResultType!), BoundNullCoalescingOperatorResultKind.LeftUnwrappedRightType => getResultStateWithRightType(leftResultType!.StrippedType(), rightResultType!), - BoundNullCoalescingOperatorResultKind.RightDynamicType => TypeWithState.Create(rightResultType!, NullableFlowState.NotNull), + BoundNullCoalescingOperatorResultKind.RightDynamicType => (rightResultType!, NullableFlowState.NotNull), _ => throw ExceptionUtilities.UnexpectedValue(node.OperatorResultKind), }; - SetResultType(node, GetNullCoalescingResultType(rightResult, resultTypeWithLeftState)); + SetResultType(node, TypeWithState.Create(resultType, rightResult.State.Join(leftState))); return null; - TypeWithState getLeftResultType(TypeSymbol leftType, TypeSymbol rightType) + (TypeSymbol ResultType, NullableFlowState LeftState) getLeftResultType(TypeSymbol leftType, TypeSymbol rightType) { Debug.Assert(rightType is object); // If there was an identity conversion between the two operands (in short, if there @@ -4224,20 +4224,20 @@ TypeWithState getLeftResultType(TypeSymbol leftType, TypeSymbol rightType) GenerateConversionForConditionalOperator(node.LeftOperand, leftType, rightType, reportMismatch: false) is { Exists: true } conversion) { Debug.Assert(!conversion.IsUserDefined); - return TypeWithState.Create(rightType, NullableFlowState.NotNull); + return (rightType, NullableFlowState.NotNull); } conversion = GenerateConversionForConditionalOperator(node.RightOperand, rightType, leftType, reportMismatch: true); Debug.Assert(!conversion.IsUserDefined); - return TypeWithState.Create(leftType, NullableFlowState.NotNull); + return (leftType, NullableFlowState.NotNull); } - TypeWithState getResultStateWithRightType(TypeSymbol leftType, TypeSymbol rightType) + (TypeSymbol ResultType, NullableFlowState LeftState) getResultStateWithRightType(TypeSymbol leftType, TypeSymbol rightType) { var conversion = GenerateConversionForConditionalOperator(node.LeftOperand, leftType, rightType, reportMismatch: true); if (conversion.IsUserDefined) { - return VisitConversion( + var conversionResult = VisitConversion( conversionOpt: null, node.LeftOperand, conversion, @@ -4251,17 +4251,14 @@ TypeWithState getResultStateWithRightType(TypeSymbol leftType, TypeSymbol rightT AssignmentKind.Assignment, reportTopLevelWarnings: false, reportRemainingWarnings: false); + Debug.Assert(conversionResult.Type is not null); + return (conversionResult.Type!, conversionResult.State); } - return TypeWithState.Create(rightType, NullableFlowState.NotNull); + return (rightType, NullableFlowState.NotNull); } } - private static TypeWithState GetNullCoalescingResultType(TypeWithState rightResult, TypeWithState resultTypeWithLeftState) - { - return TypeWithState.Create(resultTypeWithLeftState.Type, rightResult.State.Join(resultTypeWithLeftState.State)); - } - public override BoundNode? VisitConditionalAccess(BoundConditionalAccess node) { Debug.Assert(!IsConditionalState);