diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 92efec57d2812b3c46a592754e8d34b8b7c1550c..3c1c09609feba3a2cf94d740cf3766520e92c082 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -79,7 +79,7 @@ internal sealed class VariableState /// /// Types from return expressions. Used when inferring lambda return type in MethodTypeInferrer. /// - private readonly ArrayBuilder<(RefKind, TypeSymbolWithAnnotations)> _returnTypes; + private readonly ArrayBuilder<(RefKind, TypeSymbolWithAnnotations)> _returnTypesOpt; /// /// An optional callback for callers to receive notification of the inferred type and nullability @@ -100,7 +100,7 @@ internal sealed class VariableState /// /// Instances being constructed. /// - private PooledDictionary _placeholderLocals; + private PooledDictionary _placeholderLocalsOpt; /// /// For methods with annotations, we'll need to visit the arguments twice. @@ -117,7 +117,7 @@ internal sealed class VariableState protected override void Free() { _variableTypes.Free(); - _placeholderLocals?.Free(); + _placeholderLocalsOpt?.Free(); base.Free(); } @@ -128,7 +128,7 @@ protected override void Free() bool useMethodSignatureParameterTypes, MethodSymbol methodSignatureOpt, BoundNode node, - ArrayBuilder<(RefKind, TypeSymbolWithAnnotations)> returnTypes, + ArrayBuilder<(RefKind, TypeSymbolWithAnnotations)> returnTypesOpt, VariableState initialState, Action callbackOpt) : base(compilation, method, node, new EmptyStructTypeCache(compilation, dev12CompilerCompatibility: false), trackUnassignments: true) @@ -140,7 +140,7 @@ protected override void Free() _useMethodSignatureReturnType = (object)methodSignatureOpt != null && useMethodSignatureReturnType; _useMethodSignatureParameterTypes = (object)methodSignatureOpt != null && useMethodSignatureParameterTypes; _methodSignatureOpt = methodSignatureOpt; - _returnTypes = returnTypes; + _returnTypesOpt = returnTypesOpt; if (initialState != null) { var variableBySlot = initialState.VariableBySlot; @@ -168,9 +168,9 @@ protected override bool ConvertInsufficientExecutionStackExceptionToCancelledByS protected override ImmutableArray Scan(ref bool badRegion) { - if (_returnTypes != null) + if (_returnTypesOpt != null) { - _returnTypes.Clear(); + _returnTypesOpt.Clear(); } this.Diagnostics.Clear(); ParameterSymbol methodThisParameter = MethodThisParameter; @@ -439,7 +439,7 @@ protected override int MakeSlot(BoundExpression node) case BoundKind.ObjectCreationExpression: case BoundKind.DynamicObjectCreationExpression: case BoundKind.AnonymousObjectCreationExpression: - if (_placeholderLocals != null && _placeholderLocals.TryGetValue(node, out ObjectCreationPlaceholderLocal placeholder)) + if (_placeholderLocalsOpt != null && _placeholderLocalsOpt.TryGetValue(node, out ObjectCreationPlaceholderLocal placeholder)) { return GetOrCreateSlot(placeholder); } @@ -534,6 +534,8 @@ private enum AssignmentKind /// private bool ReportNullableAssignmentIfNecessary(BoundExpression value, TypeSymbolWithAnnotations targetType, TypeSymbolWithAnnotations valueType, bool useLegacyWarnings, AssignmentKind assignmentKind = AssignmentKind.Assignment, Symbol target = null) { + Debug.Assert((object)target != null || assignmentKind != AssignmentKind.Argument); + if (value == null) { return false; @@ -554,7 +556,7 @@ private bool ReportNullableAssignmentIfNecessary(BoundExpression value, TypeSymb return false; } - if (reportNullLiteralAssignmentIfNecessary()) + if (reportNullLiteralAssignmentIfNecessary(value)) { return true; } @@ -583,20 +585,20 @@ private bool ReportNullableAssignmentIfNecessary(BoundExpression value, TypeSymb // Report warning converting null literal to non-nullable reference type. // target (e.g.: `object x = null;` or calling `void F(object y)` with `F(null)`). - bool reportNullLiteralAssignmentIfNecessary() + bool reportNullLiteralAssignmentIfNecessary(BoundExpression expr) { - if (value.ConstantValue?.IsNull != true && !isDefaultOfUnconstrainedTypeParameter(value)) + if (expr.ConstantValue?.IsNull != true && !isDefaultOfUnconstrainedTypeParameter(expr)) { return false; } if (useLegacyWarnings) { - ReportWWarning(value.Syntax); + ReportWWarning(expr.Syntax); } else { - ReportDiagnostic(assignmentKind == AssignmentKind.Return ? ErrorCode.WRN_NullReferenceReturn : ErrorCode.WRN_NullAsNonNullable, value.Syntax); + ReportDiagnostic(assignmentKind == AssignmentKind.Return ? ErrorCode.WRN_NullReferenceReturn : ErrorCode.WRN_NullAsNonNullable, expr.Syntax); } return true; } @@ -1019,11 +1021,11 @@ protected override BoundNode VisitReturnStatementNoAdjust(BoundReturnStatement n return null; } - if (_returnTypes != null) + if (_returnTypesOpt != null) { // Inferring return type. Should not convert to method return type. TypeSymbolWithAnnotations result = VisitRvalueWithResult(expr); - _returnTypes.Add((node.RefKind, result)); + _returnTypesOpt.Add((node.RefKind, result)); return null; } @@ -1256,20 +1258,20 @@ private void SetResult(BoundExpression node) private ObjectCreationPlaceholderLocal GetOrCreateObjectCreationPlaceholder(BoundExpression node) { ObjectCreationPlaceholderLocal placeholder; - if (_placeholderLocals == null) + if (_placeholderLocalsOpt == null) { - _placeholderLocals = PooledDictionary.GetInstance(); + _placeholderLocalsOpt = PooledDictionary.GetInstance(); placeholder = null; } else { - _placeholderLocals.TryGetValue(node, out placeholder); + _placeholderLocalsOpt.TryGetValue(node, out placeholder); } if ((object)placeholder == null) { placeholder = new ObjectCreationPlaceholderLocal(_symbol, node); - _placeholderLocals.Add(node, placeholder); + _placeholderLocalsOpt.Add(node, placeholder); } return placeholder; @@ -3055,10 +3057,7 @@ private static Symbol AsMemberOfResultType(TypeSymbolWithAnnotations resultType, private static Symbol AsMemberOfType(NamedTypeSymbol containingType, Symbol symbol) { - if (symbol is null) - { - return null; - } + Debug.Assert((object)symbol != null); if (symbol.Kind == SymbolKind.Method) { if (((MethodSymbol)symbol).MethodKind == MethodKind.LocalFunction) @@ -3330,6 +3329,7 @@ private bool HasTopLevelNullabilityConversion(TypeSymbolWithAnnotations source, Debug.Assert(node != null); Debug.Assert(operandOpt != null || !operandType.IsNull); Debug.Assert(!targetTypeWithNullability.IsNull); + Debug.Assert((object)target != null || assignmentKind != AssignmentKind.Argument); NullableAnnotation resultAnnotation = NullableAnnotation.Unknown; bool forceOperandAnnotationForResult = false; @@ -3385,10 +3385,11 @@ private bool HasTopLevelNullabilityConversion(TypeSymbolWithAnnotations source, conversion.UserDefinedFromConversion, TypeSymbolWithAnnotations.Create(conversion.BestUserDefinedConversionAnalysis.FromType), operandType, - checkConversion: false, + checkConversion: true, fromExplicitCast: false, useLegacyWarnings, - assignmentKind); + assignmentKind, + target); // Update method based on operandType: see https://github.com/dotnet/roslyn/issues/29605. // (see NullableReferenceTypesTests.ImplicitConversions_07). @@ -3665,6 +3666,7 @@ private bool HasTopLevelNullabilityConversion(TypeSymbolWithAnnotations source, private TypeSymbolWithAnnotations ClassifyAndApplyConversion(BoundExpression node, TypeSymbolWithAnnotations targetType, TypeSymbolWithAnnotations operandType, bool useLegacyWarnings, AssignmentKind assignmentKind, ParameterSymbol target) { + Debug.Assert((object)target != null || assignmentKind != AssignmentKind.Argument); HashSet useSiteDiagnostics = null; var conversion = _conversions.ClassifyStandardConversion(null, operandType.TypeSymbol, targetType.TypeSymbol, ref useSiteDiagnostics); if (!conversion.Exists) @@ -4728,7 +4730,7 @@ public override BoundNode VisitEventAssignmentOperator(BoundEventAssignmentOpera Debug.Assert(!IsConditionalState); var receiverOpt = node.ReceiverOpt; var @event = node.Event; - if (!node.Event.IsStatic) + if (!@event.IsStatic) { @event = (EventSymbol)AsMemberOfResultType(_resultType, @event); // https://github.com/dotnet/roslyn/issues/30598: Mark receiver as not null diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index dd379807813a96f331c01562da8d9f1f104e3103..acd07fb1467c4c8c2280f1ba41a527a510faf509 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -65747,5 +65747,66 @@ static void F3(int? z, int? w) var comp = CreateCompilationWithIndexAndRange(source, options: WithNonNullTypesTrue()); comp.VerifyDiagnostics(); } + + [WorkItem(31770, "https://github.com/dotnet/roslyn/issues/31770")] + [Fact] + public void UserDefinedConversion_NestedNullability_01() + { + var source = +@"class A { } +class B +{ + public static implicit operator B(A a) => throw null; +} +class Program +{ + static void F(B b) { } + static void Main() + { + A a = new A(); + B b = a; // 1 + F(a); // 2 + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + // https://github.com/dotnet/roslyn/issues/31798: Consider improving warning to reference user-defined operator. + comp.VerifyDiagnostics( + // (12,15): warning CS8619: Nullability of reference types in value of type 'A' doesn't match target type 'A'. + // B b = a; // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "a").WithArguments("A", "A").WithLocation(12, 15), + // (13,11): warning CS8620: Nullability of reference types in argument of type 'A' doesn't match target type 'A' for parameter 'b' in 'void Program.F(B b)'. + // F(a); // 2 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgument, "a").WithArguments("A", "A", "b", "void Program.F(B b)").WithLocation(13, 11)); + } + + [Fact] + public void UserDefinedConversion_NestedNullability_02() + { + var source = +@"class A { } +class B +{ + public static implicit operator A(B b) => throw null; +} +class Program +{ + static void F(A a) { } + static void Main() + { + B b = new B(); + A a = b; // 1 + F(b); // 2 + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + // https://github.com/dotnet/roslyn/issues/31798: Consider improving warning to reference user-defined operator. + comp.VerifyDiagnostics( + // (12,24): warning CS8619: Nullability of reference types in value of type 'A' doesn't match target type 'A'. + // A a = b; // 1 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, "b").WithArguments("A", "A").WithLocation(12, 24), + // (13,11): warning CS8620: Nullability of reference types in argument of type 'A' doesn't match target type 'A' for parameter 'a' in 'void Program.F(A a)'. + // F(b); // 2 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInArgument, "b").WithArguments("A", "A", "a", "void Program.F(A a)").WithLocation(13, 11)); + } } }