Skip to content

Commit 4ca0b15

Browse files
authored
Analyze flow in receiver of extension method group in delegate creation (#60093)
1 parent e62f2cd commit 4ca0b15

File tree

2 files changed

+230
-6
lines changed

2 files changed

+230
-6
lines changed

src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs

+12-6
Original file line numberDiff line numberDiff line change
@@ -1513,15 +1513,15 @@ public override BoundNode VisitDelegateCreationExpression(BoundDelegateCreationE
15131513
var methodGroup = node.Argument as BoundMethodGroup;
15141514
if (methodGroup != null)
15151515
{
1516-
if ((object)node.MethodOpt != null && node.MethodOpt.RequiresInstanceReceiver)
1516+
if (node.MethodOpt?.OriginalDefinition is LocalFunctionSymbol localFunc)
15171517
{
1518-
EnterRegionIfNeeded(methodGroup);
1519-
VisitRvalue(methodGroup.ReceiverOpt);
1520-
LeaveRegionIfNeeded(methodGroup);
1518+
VisitLocalFunctionUse(localFunc, node.Syntax, isCall: false);
15211519
}
1522-
else if (node.MethodOpt?.OriginalDefinition is LocalFunctionSymbol localFunc)
1520+
else if (node.MethodOpt is { } method && methodGroup.ReceiverOpt is { } receiver && !ignoreReceiver(receiver, method))
15231521
{
1524-
VisitLocalFunctionUse(localFunc, node.Syntax, isCall: false);
1522+
EnterRegionIfNeeded(methodGroup);
1523+
VisitRvalue(receiver);
1524+
LeaveRegionIfNeeded(methodGroup);
15251525
}
15261526
}
15271527
else
@@ -1530,6 +1530,12 @@ public override BoundNode VisitDelegateCreationExpression(BoundDelegateCreationE
15301530
}
15311531

15321532
return null;
1533+
1534+
static bool ignoreReceiver(BoundExpression receiver, MethodSymbol method)
1535+
{
1536+
// static methods that aren't extensions get an implicit `this` receiver that should be ignored
1537+
return method.IsStatic && !method.IsExtensionMethod;
1538+
}
15331539
}
15341540

15351541
public override BoundNode VisitTypeExpression(BoundTypeExpression node)

src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs

+218
Original file line numberDiff line numberDiff line change
@@ -9175,5 +9175,223 @@ public void F(int x)
91759175
}");
91769176
Assert.Equal("x, a", GetSymbolNamesJoined(analysis.DataFlowsIn));
91779177
}
9178+
9179+
[Fact, WorkItem(59738, "https://github.com/dotnet/roslyn/issues/59738")]
9180+
public void TestDataFlowsOfIdentifierWithDelegateConversion()
9181+
{
9182+
var results = CompileAndAnalyzeDataFlowExpression(@"
9183+
using System;
9184+
9185+
internal static class NoExtensionMethods
9186+
{
9187+
internal static Func<T> AsFunc<T>(this T value) where T : class
9188+
{
9189+
return new Func<T>(/*<bind>*/ value /*</bind>*/.Return);
9190+
}
9191+
9192+
private static T Return<T>(this T value)
9193+
{
9194+
return value;
9195+
}
9196+
9197+
static void Main()
9198+
{
9199+
Console.WriteLine(((object)42).AsFunc()());
9200+
}
9201+
}
9202+
");
9203+
Assert.True(results.Succeeded);
9204+
Assert.Null(GetSymbolNamesJoined(results.Captured));
9205+
Assert.Null(GetSymbolNamesJoined(results.CapturedInside));
9206+
Assert.Null(GetSymbolNamesJoined(results.CapturedOutside));
9207+
Assert.Null(GetSymbolNamesJoined(results.VariablesDeclared));
9208+
Assert.Null(GetSymbolNamesJoined(results.DataFlowsOut));
9209+
Assert.Equal("value", GetSymbolNamesJoined(results.DefinitelyAssignedOnEntry));
9210+
Assert.Equal("value", GetSymbolNamesJoined(results.DefinitelyAssignedOnExit));
9211+
Assert.Equal("value", GetSymbolNamesJoined(results.ReadInside));
9212+
Assert.Null(GetSymbolNamesJoined(results.WrittenInside));
9213+
Assert.Null(GetSymbolNamesJoined(results.ReadOutside));
9214+
Assert.Equal("value", GetSymbolNamesJoined(results.WrittenOutside));
9215+
Assert.Null(GetSymbolNamesJoined(results.UsedLocalFunctions));
9216+
}
9217+
9218+
[Fact]
9219+
public void TestDataFlowsOfIdentifierWithDelegateConversionCast()
9220+
{
9221+
var analysis = CompileAndAnalyzeDataFlowExpression(@"
9222+
using System;
9223+
9224+
internal static class NoExtensionMethods
9225+
{
9226+
internal static Func<T> AsFunc<T>(this T value) where T : class
9227+
{
9228+
return (Func<T>)/*<bind>*/ value /*</bind>*/.Return;
9229+
}
9230+
9231+
private static T Return<T>(this T value)
9232+
{
9233+
return value;
9234+
}
9235+
}
9236+
");
9237+
Assert.True(analysis.Succeeded);
9238+
Assert.Null(GetSymbolNamesJoined(analysis.AlwaysAssigned));
9239+
Assert.Null(GetSymbolNamesJoined(analysis.Captured));
9240+
Assert.Null(GetSymbolNamesJoined(analysis.CapturedInside));
9241+
Assert.Null(GetSymbolNamesJoined(analysis.CapturedOutside));
9242+
Assert.Equal("value", GetSymbolNamesJoined(analysis.DataFlowsIn));
9243+
Assert.Equal("value", GetSymbolNamesJoined(analysis.DefinitelyAssignedOnEntry));
9244+
Assert.Equal("value", GetSymbolNamesJoined(analysis.DefinitelyAssignedOnExit));
9245+
Assert.Equal("value", GetSymbolNamesJoined(analysis.ReadInside));
9246+
Assert.Null(GetSymbolNamesJoined(analysis.ReadOutside));
9247+
Assert.Null(GetSymbolNamesJoined(analysis.VariablesDeclared));
9248+
Assert.Null(GetSymbolNamesJoined(analysis.WrittenInside));
9249+
Assert.Equal("value", GetSymbolNamesJoined(analysis.WrittenOutside));
9250+
}
9251+
9252+
[Fact]
9253+
public void TestDataFlowsOfIdentifierWithDelegateConversionTarget()
9254+
{
9255+
var analysis = CompileAndAnalyzeDataFlowExpression(@"
9256+
using System;
9257+
9258+
internal static class NoExtensionMethods
9259+
{
9260+
internal static Func<T> AsFunc<T>(this T value) where T : class
9261+
{
9262+
Func<T> result =/*<bind>*/ value /*</bind>*/.Return;
9263+
return result;
9264+
}
9265+
9266+
private static T Return<T>(this T value)
9267+
{
9268+
return value;
9269+
}
9270+
}
9271+
");
9272+
Assert.True(analysis.Succeeded);
9273+
Assert.Null(GetSymbolNamesJoined(analysis.AlwaysAssigned));
9274+
Assert.Null(GetSymbolNamesJoined(analysis.Captured));
9275+
Assert.Null(GetSymbolNamesJoined(analysis.CapturedInside));
9276+
Assert.Null(GetSymbolNamesJoined(analysis.CapturedOutside));
9277+
Assert.Equal("value", GetSymbolNamesJoined(analysis.DataFlowsIn));
9278+
Assert.Equal("value", GetSymbolNamesJoined(analysis.DefinitelyAssignedOnEntry));
9279+
Assert.Equal("value", GetSymbolNamesJoined(analysis.DefinitelyAssignedOnExit));
9280+
Assert.Equal("value", GetSymbolNamesJoined(analysis.ReadInside));
9281+
Assert.Equal("result", GetSymbolNamesJoined(analysis.ReadOutside));
9282+
Assert.Null(GetSymbolNamesJoined(analysis.VariablesDeclared));
9283+
Assert.Null(GetSymbolNamesJoined(analysis.WrittenInside));
9284+
Assert.Equal("value, result", GetSymbolNamesJoined(analysis.WrittenOutside));
9285+
}
9286+
9287+
[Fact, WorkItem(59738, "https://github.com/dotnet/roslyn/issues/59738")]
9288+
public void DefiniteAssignmentInReceiverOfExtensionMethodInDelegateCreation()
9289+
{
9290+
var comp = CreateCompilation(@"
9291+
using System;
9292+
bool b = true;
9293+
_ = new Func<string>((b ? M(out var i) : i.ToString()).ExtensionMethod);
9294+
9295+
string M(out int i)
9296+
{
9297+
throw null;
9298+
}
9299+
9300+
static class Extension
9301+
{
9302+
public static string ExtensionMethod(this string s) => throw null;
9303+
}
9304+
");
9305+
comp.VerifyDiagnostics(
9306+
// (4,42): error CS0165: Use of unassigned local variable 'i'
9307+
// _ = new Func<string>((b ? M(out var i) : i.ToString()).ExtensionMethod);
9308+
Diagnostic(ErrorCode.ERR_UseDefViolation, "i").WithArguments("i").WithLocation(4, 42)
9309+
);
9310+
}
9311+
9312+
[Fact, WorkItem(59738, "https://github.com/dotnet/roslyn/issues/59738")]
9313+
public void DefiniteAssignmentShouldSkipImplicitThisInStaticMethodConversion()
9314+
{
9315+
var comp = CreateCompilation(@"
9316+
using System;
9317+
public struct C
9318+
{
9319+
private object field;
9320+
public C(Action a)
9321+
{
9322+
// implicit `this` receiver should be ignored in definite assignment
9323+
a = new(M);
9324+
field = 1;
9325+
}
9326+
9327+
public C(Action a, int ignored)
9328+
{
9329+
// implicit `this` receiver should be ignored in definite assignment
9330+
a = new Action(M);
9331+
field = 1;
9332+
}
9333+
9334+
public void Method1(Action a)
9335+
{
9336+
// explicit `this` disallowed
9337+
a = new Action(this.M);
9338+
}
9339+
9340+
public void Method2(Action a, C c)
9341+
{
9342+
// instance receiver disallowed
9343+
a = new Action(c.M);
9344+
}
9345+
9346+
private static void M()
9347+
{
9348+
}
9349+
}
9350+
");
9351+
comp.VerifyDiagnostics(
9352+
// (23,24): error CS0176: Member 'C.M()' cannot be accessed with an instance reference; qualify it with a type name instead
9353+
// a = new Action(this.M);
9354+
Diagnostic(ErrorCode.ERR_ObjectProhibited, "this.M").WithArguments("C.M()").WithLocation(23, 24),
9355+
// (29,24): error CS0176: Member 'C.M()' cannot be accessed with an instance reference; qualify it with a type name instead
9356+
// a = new Action(c.M);
9357+
Diagnostic(ErrorCode.ERR_ObjectProhibited, "c.M").WithArguments("C.M()").WithLocation(29, 24)
9358+
);
9359+
}
9360+
9361+
[Fact, WorkItem(59738, "https://github.com/dotnet/roslyn/issues/59738")]
9362+
public void DefiniteAssignmentWithExplicitThisInStaticMethodConversion()
9363+
{
9364+
var comp = CreateCompilation(@"
9365+
using System;
9366+
public struct C
9367+
{
9368+
private object field;
9369+
public void Method1(Action a)
9370+
{
9371+
a = new Action(this.M);
9372+
field = 1;
9373+
}
9374+
9375+
public void Method2(Action a, C c)
9376+
{
9377+
a = new Action(c.M);
9378+
}
9379+
}
9380+
public static class Extension
9381+
{
9382+
public static void M(this C c)
9383+
{
9384+
}
9385+
}
9386+
");
9387+
comp.VerifyDiagnostics(
9388+
// (8,24): error CS1113: Extension method 'Extension.M(C)' defined on value type 'C' cannot be used to create delegates
9389+
// a = new Action(this.M);
9390+
Diagnostic(ErrorCode.ERR_ValueTypeExtDelegate, "this.M").WithArguments("Extension.M(C)", "C").WithLocation(8, 24),
9391+
// (14,24): error CS1113: Extension method 'Extension.M(C)' defined on value type 'C' cannot be used to create delegates
9392+
// a = new Action(c.M);
9393+
Diagnostic(ErrorCode.ERR_ValueTypeExtDelegate, "c.M").WithArguments("Extension.M(C)", "C").WithLocation(14, 24)
9394+
);
9395+
}
91789396
}
91799397
}

0 commit comments

Comments
 (0)