diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index ab919a24c97a21831a07dc2621db69040f90380c..4e1bc7a93c5dfffd8ccf1e7bd4f04cff27b837ab 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -146,6 +146,12 @@ public VisitArgumentResult(VisitResult visitResult, Optional stateFo // https://github.com/dotnet/roslyn/issues/35043: remove this when all expression are supported private bool _disableNullabilityAnalysis; + /// + /// State of method group receivers, used later when analyzing the conversion to a delegate. + /// (Could be replaced by _analyzedNullabilityMapOpt if that map is always available.) + /// + private PooledDictionary _methodGroupReceiverMapOpt; + #if DEBUG /// /// Contains the expressions that should not be inserted into . @@ -291,6 +297,7 @@ private void SetAnalyzedNullability(BoundExpression expr, VisitResult result, bo protected override void Free() { + _methodGroupReceiverMapOpt?.Free(); _variableTypes.Free(); _placeholderLocalsOpt?.Free(); base.Free(); @@ -4044,7 +4051,7 @@ public override BoundNode VisitTupleBinaryOperator(BoundTupleBinaryOperator node return null; } - private void ReportNullabilityMismatchWithTargetDelegate(Location location, NamedTypeSymbol delegateType, MethodSymbol method) + private void ReportNullabilityMismatchWithTargetDelegate(Location location, NamedTypeSymbol delegateType, MethodSymbol method, bool invokedAsExtensionMethod) { Debug.Assert((object)method != null); Debug.Assert(method.MethodKind != MethodKind.LambdaMethod); @@ -4062,11 +4069,12 @@ private void ReportNullabilityMismatchWithTargetDelegate(Location location, Name delegateType); } - int count = Math.Min(invoke.ParameterCount, method.ParameterCount); + int methodOffset = invokedAsExtensionMethod ? 1 : 0; + int count = Math.Min(invoke.ParameterCount, method.ParameterCount - methodOffset); for (int i = 0; i < count; i++) { var invokeParameter = invoke.Parameters[i]; - var methodParameter = method.Parameters[i]; + var methodParameter = method.Parameters[i + methodOffset]; if (IsNullabilityMismatch(invokeParameter.TypeWithAnnotations, methodParameter.TypeWithAnnotations, requireIdentity: invokeParameter.RefKind != RefKind.None)) { ReportSafetyDiagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, location, @@ -4182,9 +4190,25 @@ private bool HasTopLevelNullabilityConversion(TypeWithAnnotations source, TypeWi switch (conversion.Kind) { case ConversionKind.MethodGroup: - if (reportRemainingWarnings) { - ReportNullabilityMismatchWithTargetDelegate(location, targetType.GetDelegateType(), conversion.Method); + var receiverOpt = (operandOpt as BoundMethodGroup)?.ReceiverOpt; + // https://github.com/dotnet/roslyn/issues/33637: Should update method based on inferred receiver type. + var method = conversion.Method; + if (TryGetMethodGroupReceiverNullability(receiverOpt, out TypeWithState receiverType)) + { + if (conversion.IsExtensionMethod) + { + CheckExtensionMethodThisNullability(receiverOpt, Conversion.Identity, method.Parameters[0], receiverType); + } + else + { + CheckPossibleNullReceiver(receiverOpt, receiverType, checkNullableValueType: false); + } + } + if (reportRemainingWarnings) + { + ReportNullabilityMismatchWithTargetDelegate(location, targetType.GetDelegateType(), method, conversion.IsExtensionMethod); + } } resultState = NullableFlowState.NotNull; break; @@ -4195,14 +4219,7 @@ private bool HasTopLevelNullabilityConversion(TypeWithAnnotations source, TypeWi var lambda = (BoundLambda)operandOpt; var delegateType = targetType.GetDelegateType(); var variableState = GetVariableState(stateForLambda); - Analyze(compilation, - lambda, - _conversions, - Diagnostics, - delegateInvokeMethod: delegateType?.DelegateInvokeMethod, - returnTypes: null, - initialState: variableState, - analyzedNullabilityMapOpt: _disableNullabilityAnalysis ? null : _analyzedNullabilityMapOpt); + VisitLambda(lambda, delegateType, Diagnostics, variableState); if (reportRemainingWarnings) { ReportNullabilityMismatchWithTargetDelegate(location, delegateType, lambda.UnboundLambda); @@ -4612,19 +4629,34 @@ public override BoundNode VisitDelegateCreationExpression(BoundDelegateCreationE { case BoundMethodGroup group: { - VisitRvalue(group.ReceiverOpt); - SetAnalyzedNullability(group, default); // https://github.com/dotnet/roslyn/issues/33637: Should update method based on inferred receiver type. var method = node.MethodOpt; - if (!(method is null) && !group.IsSuppressed) + if (method is object) { - ReportNullabilityMismatchWithTargetDelegate(group.Syntax.Location, delegateType, method); + var receiverOpt = group.ReceiverOpt; + if (receiverOpt != null) + { + VisitRvalue(receiverOpt); + if (node.IsExtensionMethod) + { + CheckExtensionMethodThisNullability(receiverOpt, Conversion.Identity, method.Parameters[0], ResultType); + } + else + { + CheckPossibleNullReceiver(receiverOpt); + } + } + if (!group.IsSuppressed) + { + ReportNullabilityMismatchWithTargetDelegate(group.Syntax.Location, delegateType, method, node.IsExtensionMethod); + } } + SetAnalyzedNullability(group, default); } break; case BoundLambda lambda: { - VisitLambda(lambda, Diagnostics); + VisitLambda(lambda, delegateType, Diagnostics); SetNotNullResult(lambda); if (!lambda.IsSuppressed) { @@ -4649,17 +4681,37 @@ public override BoundNode VisitMethodGroup(BoundMethodGroup node) if (receiverOpt != null) { VisitRvalue(receiverOpt); - // https://github.com/dotnet/roslyn/issues/30563: Should not check receiver here. - // That check should be handled when applying the method group conversion, + // Receiver nullability is checked when applying the method group conversion, // when we have a specific method, to avoid reporting null receiver warnings - // for extension method delegates. - _ = CheckPossibleNullReceiver(receiverOpt); + // for extension method delegates. Here, store the receiver state for that check. + SetMethodGroupReceiverNullability(receiverOpt, ResultType); } SetNotNullResult(node); return null; } + private bool TryGetMethodGroupReceiverNullability(BoundExpression receiverOpt, out TypeWithState type) + { + if (receiverOpt != null && + _methodGroupReceiverMapOpt != null && + _methodGroupReceiverMapOpt.TryGetValue(receiverOpt, out type)) + { + return true; + } + else + { + type = default; + return false; + } + } + + private void SetMethodGroupReceiverNullability(BoundExpression receiver, TypeWithState type) + { + _methodGroupReceiverMapOpt ??= PooledDictionary.GetInstance(); + _methodGroupReceiverMapOpt[receiver] = type; + } + public override BoundNode VisitLambda(BoundLambda node) { // It's possible to reach VisitLambda without having analyzed the lambda body in error scenarios, @@ -4673,16 +4725,25 @@ public override BoundNode VisitLambda(BoundLambda node) if (!_disableNullabilityAnalysis) { var bag = new DiagnosticBag(); - VisitLambda(node, bag); + VisitLambda(node, node.Type.GetDelegateType(), bag); bag.Free(); } SetNotNullResult(node); return null; } - private void VisitLambda(BoundLambda node, DiagnosticBag diagnostics) + private void VisitLambda(BoundLambda node, NamedTypeSymbol delegateTypeOpt, DiagnosticBag diagnostics, VariableState initialState = null) { - Analyze(compilation, node, _conversions, diagnostics, node.Type.GetDelegateType()?.DelegateInvokeMethod, returnTypes: null, initialState: GetVariableState(State.Clone()), _analyzedNullabilityMapOpt); + Debug.Assert(delegateTypeOpt?.IsDelegateType() != false); + Analyze( + compilation, + node, + _conversions, + diagnostics, + delegateTypeOpt?.DelegateInvokeMethod, + returnTypes: null, + initialState: initialState ?? GetVariableState(State.Clone()), + _disableNullabilityAnalysis ? null : _analyzedNullabilityMapOpt); } public override BoundNode VisitUnboundLambda(UnboundLambda node) @@ -4690,14 +4751,7 @@ public override BoundNode VisitUnboundLambda(UnboundLambda node) // The presence of this node suggests an error was detected in an earlier phase. // Analyze the body to report any additional warnings. var lambda = node.BindForErrorRecovery(); - Analyze(compilation, - lambda, - _conversions, - Diagnostics, - delegateInvokeMethod: null, - returnTypes: null, - initialState: GetVariableState(State.Clone()), - _disableNullabilityAnalysis ? null : _analyzedNullabilityMapOpt); + VisitLambda(lambda, delegateTypeOpt: null, Diagnostics); SetNotNullResult(node); return null; } @@ -4845,7 +4899,6 @@ private void VisitDeconstructionArguments(ArrayBuilder v SetResultType(right, rightResultOpt.Value); } var rightResult = ResultType; - var rightResultWithAnnotations = rightResult.ToTypeWithAnnotations(); var invocation = conversion.DeconstructionInfo.Invocation as BoundCall; var deconstructMethod = invocation?.Method; @@ -4865,17 +4918,13 @@ private void VisitDeconstructionArguments(ArrayBuilder v else { // Check nullability for `this` parameter - var parameter = deconstructMethod.Parameters[0]; - VisitArgumentConversion( - right, conversion, parameter.RefKind, parameter, parameter.TypeWithAnnotations, - new VisitArgumentResult(new VisitResult(rightResult, rightResultWithAnnotations), stateForLambda: default), - extensionMethodThisArgument: true); + CheckExtensionMethodThisNullability(right, conversion, deconstructMethod.Parameters[0], rightResult); if (deconstructMethod.IsGenericMethod) { // re-infer the deconstruct parameters based on the 'this' parameter ArrayBuilder placeholderArgs = ArrayBuilder.GetInstance(n + 1); - placeholderArgs.Add(CreatePlaceholderIfNecessary(right, rightResultWithAnnotations)); + placeholderArgs.Add(CreatePlaceholderIfNecessary(right, rightResult.ToTypeWithAnnotations())); for (int i = 0; i < n; i++) { placeholderArgs.Add(new BoundExpressionWithNullability(variables[i].Expression.Syntax, variables[i].Expression, NullableAnnotation.Oblivious, conversion.DeconstructionInfo.OutputPlaceholders[i].Type)); @@ -6113,7 +6162,16 @@ public override BoundNode VisitDynamicMemberAccess(BoundDynamicMemberAccess node public override BoundNode VisitDynamicInvocation(BoundDynamicInvocation node) { - VisitRvalue(node.Expression); + var expr = node.Expression; + VisitRvalue(expr); + + // If the expression was a MethodGroup, check nullability of receiver. + var receiverOpt = (expr as BoundMethodGroup)?.ReceiverOpt; + if (TryGetMethodGroupReceiverNullability(receiverOpt, out TypeWithState receiverType)) + { + CheckPossibleNullReceiver(receiverOpt, receiverType, checkNullableValueType: false); + } + VisitArgumentsEvaluate(node.Arguments, node.ArgumentRefKindsOpt); Debug.Assert(node.Type.IsDynamic()); Debug.Assert(node.Type.IsReferenceType); @@ -6234,12 +6292,17 @@ public override BoundNode VisitDynamicIndexerAccess(BoundDynamicIndexerAccess no } private bool CheckPossibleNullReceiver(BoundExpression receiverOpt, bool checkNullableValueType = false) + { + return CheckPossibleNullReceiver(receiverOpt, ResultType, checkNullableValueType); + } + + private bool CheckPossibleNullReceiver(BoundExpression receiverOpt, TypeWithState resultType, bool checkNullableValueType) { Debug.Assert(!this.IsConditionalState); bool reportedDiagnostic = false; if (receiverOpt != null && this.State.Reachable) { - var resultTypeSymbol = ResultType.Type; + var resultTypeSymbol = resultType.Type; if (resultTypeSymbol is null) { return false; @@ -6247,7 +6310,7 @@ private bool CheckPossibleNullReceiver(BoundExpression receiverOpt, bool checkNu #if DEBUG Debug.Assert(receiverOpt.Type is null || AreCloseEnough(receiverOpt.Type, resultTypeSymbol)); #endif - if (!ReportPossibleNullReceiverIfNeeded(resultTypeSymbol, ResultType.State, checkNullableValueType, receiverOpt.Syntax, out reportedDiagnostic)) + if (!ReportPossibleNullReceiverIfNeeded(resultTypeSymbol, resultType.State, checkNullableValueType, receiverOpt.Syntax, out reportedDiagnostic)) { return reportedDiagnostic; } @@ -6277,6 +6340,19 @@ private bool ReportPossibleNullReceiverIfNeeded(TypeSymbol type, NullableFlowSta return true; } + private void CheckExtensionMethodThisNullability(BoundExpression expr, Conversion conversion, ParameterSymbol parameter, TypeWithState result) + { + VisitArgumentConversion( + expr, + conversion, + parameter.RefKind, + parameter, + parameter.TypeWithAnnotations, + new VisitArgumentResult(new VisitResult(result, result.ToTypeWithAnnotations()), + stateForLambda: default), + extensionMethodThisArgument: true); + } + private static bool IsNullabilityMismatch(TypeWithAnnotations type1, TypeWithAnnotations type2) { // Note, when we are paying attention to nullability, we ignore oblivious mismatch. diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index c1a846b7f081e72add901c6af70b4e5270982bb1..e3caceb9f8fd23925ffd045c2d64cf553e293ff8 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -9840,7 +9840,6 @@ public void M(T t) "; var compilation = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); - compilation.VerifyTypes(); compilation.VerifyDiagnostics( // (11,13): warning CS8600: Converting null literal or possible null value to non-nullable type. // t = null; // warn @@ -9869,15 +9868,15 @@ void M() MyDelegate x1 = Method; MyDelegate x2 = FalseMethod; MyDelegate x4 = NullableReturnMethod; // warn 1 - MyDelegate x5 = NullableParameterMethod; // warn 2 + MyDelegate x5 = NullableParameterMethod; MyFalseDelegate y1 = Method; MyFalseDelegate y2 = FalseMethod; MyFalseDelegate y4 = NullableReturnMethod; MyFalseDelegate y5 = NullableParameterMethod; - MyNullableDelegate z1 = Method; // warn 3 + MyNullableDelegate z1 = Method; // warn 2 MyNullableDelegate z2 = FalseMethod; - MyNullableDelegate z4 = NullableReturnMethod; // warn 4 - MyNullableDelegate z5 = NullableParameterMethod; // warn 5 + MyNullableDelegate z4 = NullableReturnMethod; // warn 3 + MyNullableDelegate z5 = NullableParameterMethod; } " + NonNullTypesOn() + @" @@ -9892,20 +9891,17 @@ void M() "; var compilation = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); - compilation.VerifyTypes(); compilation.VerifyDiagnostics( // (18,25): warning CS8621: Nullability of reference types in return type of 'string[]? C.NullableReturnMethod(string[] x)' doesn't match the target delegate 'MyDelegate'. // MyDelegate x4 = NullableReturnMethod; // warn 1 Diagnostic(ErrorCode.WRN_NullabilityMismatchInReturnTypeOfTargetDelegate, "NullableReturnMethod").WithArguments("string[]? C.NullableReturnMethod(string[] x)", "MyDelegate").WithLocation(18, 25), // (24,33): warning CS8622: Nullability of reference types in type of parameter 'x' of 'string[] C.Method(string[] x)' doesn't match the target delegate 'MyNullableDelegate'. - // MyNullableDelegate z1 = Method; // warn 3 + // MyNullableDelegate z1 = Method; // warn 2 Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, "Method").WithArguments("x", "string[] C.Method(string[] x)", "MyNullableDelegate").WithLocation(24, 33), // (26,33): warning CS8622: Nullability of reference types in type of parameter 'x' of 'string[]? C.NullableReturnMethod(string[] x)' doesn't match the target delegate 'MyNullableDelegate'. - // MyNullableDelegate z4 = NullableReturnMethod; // warn 4 + // MyNullableDelegate z4 = NullableReturnMethod; // warn 3 Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, "NullableReturnMethod").WithArguments("x", "string[]? C.NullableReturnMethod(string[] x)", "MyNullableDelegate").WithLocation(26, 33) ); - - // https://github.com/dotnet/roslyn/issues/29844: Missing warnings 2 and 5 } [Fact] @@ -9946,7 +9942,6 @@ void M() "; var compilation = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); - compilation.VerifyTypes(); compilation.VerifyDiagnostics( // (27,15): warning CS8604: Possible null reference argument for parameter 'x' in 'C.C(string[] x)'. // new C(nullableField); // warn @@ -33550,6 +33545,30 @@ class Program Diagnostic(ErrorCode.WRN_NullReferenceReturn, "F()").WithLocation(7, 28)); } + [Fact] + public void DelegateCreation_09() + { + var source = +@"delegate void D(); +class C +{ + void F() { } + static void M() + { + D d = default(C).F; // 1 + _ = new D(default(C).F); // 2 + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (7,15): warning CS8602: Dereference of a possibly null reference. + // D d = default(C).F; // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "default(C)").WithLocation(7, 15), + // (8,19): warning CS8602: Dereference of a possibly null reference. + // _ = new D(default(C).F); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "default(C)").WithLocation(8, 19)); + } + [Fact] [WorkItem(32499, "https://github.com/dotnet/roslyn/issues/32499")] public void DelegateCreation_ReturnType_01() @@ -72884,14 +72903,10 @@ static class E internal static void F2(this C? c) { } }"; var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); - // https://github.com/dotnet/roslyn/issues/30563: Should not report "CS8602: Possible dereference" for y.F2. comp.VerifyDiagnostics( // (8,13): warning CS8602: Dereference of a possibly null reference. // d = x.F1; // warning - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 13), - // (9,13): warning CS8602: Dereference of a possibly null reference. - // d = y.F2; // ok - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(9, 13)); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(8, 13)); } [WorkItem(30563, "https://github.com/dotnet/roslyn/issues/30563")] @@ -72919,15 +72934,16 @@ static class E internal static void F(this C x) { } }"; var comp = CreateCompilation(new[] { source }, options: WithNonNullTypesTrue()); - // https://github.com/dotnet/roslyn/issues/30563: Should not report "CS8602: Possible dereference" - // for F2(y.F). Should report "CS8604: Possible null reference argument" instead. comp.VerifyDiagnostics( // (10,12): warning CS8602: Dereference of a possibly null reference. // F1(x.F); // 1 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x").WithLocation(10, 12), - // (12,12): warning CS8602: Dereference of a possibly null reference. + // (12,12): warning CS8604: Possible null reference argument for parameter 'x' in 'void E.F(C x)'. // F2(y.F); // 2 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "y").WithLocation(12, 12)); + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "y").WithArguments("x", "void E.F(C x)").WithLocation(12, 12), + // (13,12): warning CS8604: Possible null reference argument for parameter 'x' in 'void E.F(C x)'. + // F2(y.F); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "y").WithArguments("x", "void E.F(C x)").WithLocation(13, 12)); } [WorkItem(33637, "https://github.com/dotnet/roslyn/issues/33637")] @@ -95923,5 +95939,292 @@ void M() Diagnostic(ErrorCode.WRN_NullabilityMismatchInAssignment, @"new[] { new[] { ""string"" , null } }").WithArguments("string?[][]", "System.Collections.Generic.IEnumerable>").WithLocation(11, 47) ); } + + [Fact] + [WorkItem(30563, "https://github.com/dotnet/roslyn/issues/30563")] + public void ExtensionMethodDelegate_01() + { + var source = +@"delegate void D0(); +delegate void D1(T t); +static class E +{ + internal static void F(this T t) { } +} +class Program +{ + static void M0(string? x, string y) + { + D0 d; + d = x.F; + d = x.F; + d = x.F; // 1 + d = y.F; + d = y.F; + d = y.F; + } + static void M1() + { + D1 d; + D1 e; + d = E.F; + d = E.F; // 2 + e = E.F; + e = E.F; + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (14,13): warning CS8604: Possible null reference argument for parameter 't' in 'void E.F(string t)'. + // d = x.F; // 1 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "x").WithArguments("t", "void E.F(string t)").WithLocation(14, 13), + // (24,13): warning CS8622: Nullability of reference types in type of parameter 't' of 'void E.F(string t)' doesn't match the target delegate 'D1'. + // d = E.F; // 2 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, "E.F").WithArguments("t", "void E.F(string t)", "D1").WithLocation(24, 13)); + } + + [Fact] + public void ExtensionMethodDelegate_02() + { + var source = +@"delegate void D0(); +delegate void D1(T t); +static class E +{ + internal static void F(this T t) { } +} +class Program +{ + static void M0(string? x, string y) + { + _ = new D0(x.F); + _ = new D0(x.F); + _ = new D0(x.F); // 1 + _ = new D0(y.F); + _ = new D0(y.F); + _ = new D0(y.F); + } + static void M1() + { + _ = new D1(E.F); + _ = new D1(E.F); // 2 + _ = new D1(E.F); + _ = new D1(E.F); + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (13,20): warning CS8604: Possible null reference argument for parameter 't' in 'void E.F(string t)'. + // _ = new D0(x.F); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "x").WithArguments("t", "void E.F(string t)").WithLocation(13, 20), + // (21,29): warning CS8622: Nullability of reference types in type of parameter 't' of 'void E.F(string t)' doesn't match the target delegate 'D1'. + // _ = new D1(E.F); // 2 + Diagnostic(ErrorCode.WRN_NullabilityMismatchInParameterTypeOfTargetDelegate, "E.F").WithArguments("t", "void E.F(string t)", "D1").WithLocation(21, 29)); + } + + [Fact] + [WorkItem(30287, "https://github.com/dotnet/roslyn/issues/30287")] + [WorkItem(30563, "https://github.com/dotnet/roslyn/issues/30563")] + public void ExtensionMethodDelegate_03() + { + var source = +@"delegate void D(T t); +static class E +{ + internal static void F(this T t, int i) { } +} +class Program +{ + static void M(string? x, string y) + { + D d; + d = x.F; + d = x.F; + d = x.F; // 1 + d = y.F; + d = y.F; + d = y.F; + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (13,13): warning CS8604: Possible null reference argument for parameter 't' in 'void E.F(string t, int i)'. + // d = x.F; // 1 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "x").WithArguments("t", "void E.F(string t, int i)").WithLocation(13, 13)); + } + + [Fact] + [WorkItem(30287, "https://github.com/dotnet/roslyn/issues/30287")] + public void ExtensionMethodDelegate_04() + { + var source = +@"delegate void D(T t); +static class E +{ + internal static void F(this T t, int i) { } +} +class Program +{ + static void M(string? x, string y) + { + _ = new D(x.F); + _ = new D(x.F); + _ = new D(x.F); // 1 + _ = new D(y.F); + _ = new D(y.F); + _ = new D(y.F); + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (12,24): warning CS8604: Possible null reference argument for parameter 't' in 'void E.F(string t, int i)'. + // _ = new D(x.F); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "x").WithArguments("t", "void E.F(string t, int i)").WithLocation(12, 24)); + } + + [Fact] + [WorkItem(30287, "https://github.com/dotnet/roslyn/issues/30287")] + [WorkItem(30563, "https://github.com/dotnet/roslyn/issues/30563")] + public void ExtensionMethodDelegate_05() + { + var source = +@"delegate void D(T t); +static class E +{ + internal static void F(this T t, U u) { } +} +class Program +{ + static void M(string? x, string y) + { + D d; + D e; + d = x.F; + e = x.F; + d = x.F; // 1 + e = x.F; // 2 + d = y.F; + e = y.F; + d = y.F; + e = y.F; + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (14,13): warning CS8604: Possible null reference argument for parameter 't' in 'void E.F(string t, string? u)'. + // d = x.F; // 1 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "x").WithArguments("t", "void E.F(string t, string? u)").WithLocation(14, 13), + // (15,13): warning CS8604: Possible null reference argument for parameter 't' in 'void E.F(string t, string u)'. + // e = x.F; // 2 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "x").WithArguments("t", "void E.F(string t, string u)").WithLocation(15, 13)); + } + + [Fact] + [WorkItem(30287, "https://github.com/dotnet/roslyn/issues/30287")] + public void ExtensionMethodDelegate_06() + { + var source = +@"delegate void D(T t); +static class E +{ + internal static void F(this T t, U u) { } +} +class Program +{ + static void M(string? x, string y) + { + _ = new D(x.F); + _ = new D(x.F); + _ = new D(x.F); // 1 + _ = new D(x.F); // 2 + _ = new D(y.F); + _ = new D(y.F); + _ = new D(y.F); + _ = new D(y.F); + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (12,28): warning CS8604: Possible null reference argument for parameter 't' in 'void E.F(string t, string? u)'. + // _ = new D(x.F); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "x").WithArguments("t", "void E.F(string t, string? u)").WithLocation(12, 28), + // (13,27): warning CS8604: Possible null reference argument for parameter 't' in 'void E.F(string t, string u)'. + // _ = new D(x.F); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceArgument, "x").WithArguments("t", "void E.F(string t, string u)").WithLocation(13, 27)); + } + + [Fact] + [WorkItem(30563, "https://github.com/dotnet/roslyn/issues/30563")] + public void ExtensionMethodDelegate_07() + { + var source = +@"delegate void D(); +static class E +{ + internal static void F(this T t) { } +} +class Program +{ + static void M() + where U : class + where V : struct + { + D d; + d = default(T).F; + d = default(U).F; + d = default(V).F; + d = default(V?).F; + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (13,13): error CS1113: Extension method 'E.F(T)' defined on value type 'T' cannot be used to create delegates + // d = default(T).F; + Diagnostic(ErrorCode.ERR_ValueTypeExtDelegate, "default(T).F").WithArguments("E.F(T)", "T").WithLocation(13, 13), + // (13,13): warning CS8653: A default expression introduces a null value when 'T' is a non-nullable reference type. + // d = default(T).F; + Diagnostic(ErrorCode.WRN_DefaultExpressionMayIntroduceNullT, "default(T)").WithArguments("T").WithLocation(13, 13), + // (15,13): error CS1113: Extension method 'E.F(V)' defined on value type 'V' cannot be used to create delegates + // d = default(V).F; + Diagnostic(ErrorCode.ERR_ValueTypeExtDelegate, "default(V).F").WithArguments("E.F(V)", "V").WithLocation(15, 13), + // (16,13): error CS1113: Extension method 'E.F(V?)' defined on value type 'V?' cannot be used to create delegates + // d = default(V?).F; + Diagnostic(ErrorCode.ERR_ValueTypeExtDelegate, "default(V?).F").WithArguments("E.F(V?)", "V?").WithLocation(16, 13)); + } + + [Fact] + [WorkItem(30563, "https://github.com/dotnet/roslyn/issues/30563")] + public void ExtensionMethodDelegate_08() + { + var source = +@"delegate void D(); +static class E +{ + internal static void F(this T t) { } +} +class Program +{ + static void M() + where U : class + where V : struct + { + _ = new D(default(T).F); + _ = new D(default(U).F); + _ = new D(default(V).F); + _ = new D(default(V?).F); + } +}"; + var comp = CreateCompilation(source, options: WithNonNullTypesTrue()); + comp.VerifyDiagnostics( + // (12,19): error CS1113: Extension method 'E.F(T)' defined on value type 'T' cannot be used to create delegates + // _ = new D(default(T).F); + Diagnostic(ErrorCode.ERR_ValueTypeExtDelegate, "default(T).F").WithArguments("E.F(T)", "T").WithLocation(12, 19), + // (14,19): error CS1113: Extension method 'E.F(V)' defined on value type 'V' cannot be used to create delegates + // _ = new D(default(V).F); + Diagnostic(ErrorCode.ERR_ValueTypeExtDelegate, "default(V).F").WithArguments("E.F(V)", "V").WithLocation(14, 19), + // (15,19): error CS1113: Extension method 'E.F(V?)' defined on value type 'V?' cannot be used to create delegates + // _ = new D(default(V?).F); + Diagnostic(ErrorCode.ERR_ValueTypeExtDelegate, "default(V?).F").WithArguments("E.F(V?)", "V?").WithLocation(15, 19)); + } } }