Skip to content

Commit f4759c0

Browse files
Handle extension methods which create delegates
The first parameter appears as the "instance" for a reduced extension method
1 parent 2e3fe58 commit f4759c0

2 files changed

Lines changed: 40 additions & 4 deletions

File tree

NullabilityInference.Tests/InferenceTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,41 @@ public class SomeContext
591591
");
592592
}
593593

594+
/// <summary>
595+
/// FUTURE: NotNullWhenNotNullAttribute on IfNull would allow Array to be marked non-null
596+
/// </summary>
597+
[Fact]
598+
public void ExtensionMethodDelegate()
599+
{
600+
AssertNullabilityInference(@"
601+
using System.Linq;
602+
603+
public static class SomeStatic
604+
{
605+
public static void Main()
606+
{
607+
object?[] x = new[] { new object() }.Select(7.IfNull).ToArray();
608+
}
609+
public static object? IfNull(this object? a, object? b) => b ?? a;
610+
}");
611+
}
612+
613+
[Fact]
614+
public void InstanceMethodDelegate()
615+
{
616+
AssertNullabilityInference(@"
617+
using System.Linq;
618+
619+
public class SomeInstance
620+
{
621+
public void Caller()
622+
{
623+
object[] x = new[] { new object() }.Select(IfNull).ToArray();
624+
}
625+
public object IfNull(object? b) => b ?? this;
626+
}");
627+
}
628+
594629
[Fact]
595630
public void CompareNullableValueTypeToNullDoesNotThrow()
596631
{

NullabilityInference/EdgeBuildingOperationVisitor.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,7 @@ private TypeWithNode[] ExtendMethodTypeArguments(IMethodSymbol targetMethod, Typ
957957
return methodTypeArgNodes;
958958
}
959959

960-
private void HandleMethodGroup(IMethodReferenceOperation operation, TypeWithNode delegateReturnType, TypeWithNode[] delegateParameters)
960+
private void HandleMethodGroup(IMethodReferenceOperation operation, TypeWithNode delegateReturnType, IReadOnlyCollection<TypeWithNode> delegateParameters)
961961
{
962962
var receiverType = GetReceiverType(operation);
963963
var classTypeArgNodes = ClassTypeArgumentsForMemberAccess(receiverType, operation.Method);
@@ -970,7 +970,7 @@ private void HandleMethodGroup(IMethodReferenceOperation operation, TypeWithNode
970970
var substitution = new TypeSubstitution(classTypeArgNodes, methodTypeArgNodes);
971971
EdgeLabel label = new EdgeLabel($"MethodGroup", operation);
972972

973-
Debug.Assert(operation.Method.Parameters.Length == delegateParameters.Length);
973+
Debug.Assert(operation.Method.Parameters.Length == delegateParameters.Count);
974974
foreach (var (methodParam, delegateParam) in operation.Method.Parameters.Zip(delegateParameters)) {
975975
var methodParamType = typeSystem.GetSymbolType(methodParam.OriginalDefinition);
976976
methodParamType = methodParamType.WithSubstitution(methodParam.Type, substitution, tsBuilder);
@@ -1608,7 +1608,7 @@ public override TypeWithNode VisitDelegateCreation(IDelegateCreationOperation op
16081608
var delegateReturnType = typeSystem.GetSymbolType(delegateType.DelegateInvokeMethod.OriginalDefinition);
16091609
delegateReturnType = delegateReturnType.WithSubstitution(delegateType.DelegateInvokeMethod.ReturnType, substitution, tsBuilder);
16101610
var delegateParameters = delegateType.DelegateInvokeMethod.Parameters
1611-
.Select(p => typeSystem.GetSymbolType(p.OriginalDefinition).WithSubstitution(p.Type, substitution, tsBuilder)).ToArray();
1611+
.Select(p => typeSystem.GetSymbolType(p.OriginalDefinition).WithSubstitution(p.Type, substitution, tsBuilder)).ToList();
16121612
switch (operation.Target) {
16131613
case IAnonymousFunctionOperation lambda:
16141614
// Create edges for lambda parameters
@@ -1629,7 +1629,7 @@ public override TypeWithNode VisitDelegateCreation(IDelegateCreationOperation op
16291629
};
16301630
}
16311631
if (parameterList != null) {
1632-
Debug.Assert(parameterList.Count == delegateParameters.Length);
1632+
Debug.Assert(parameterList.Count == delegateParameters.Count);
16331633
foreach (var (lambdaParamSyntax, invokeParam) in parameterList.Zip(delegateParameters)) {
16341634
if (lambdaParamSyntax.Type != null) {
16351635
var paramType = lambdaParamSyntax.Type.Accept(syntaxVisitor);
@@ -1665,6 +1665,7 @@ public override TypeWithNode VisitDelegateCreation(IDelegateCreationOperation op
16651665
}
16661666
break;
16671667
case IMethodReferenceOperation methodReference:
1668+
if (methodReference.Instance != null && methodReference.Method.IsExtensionMethod) delegateParameters.Insert(0, methodReference.Instance.Accept(this, EdgeBuildingContext.Normal));
16681669
HandleMethodGroup(methodReference, delegateReturnType, delegateParameters);
16691670
break;
16701671
default:

0 commit comments

Comments
 (0)