Skip to content

Fix Local-Symbol Containing-Symbol in New-Extension-Method-Rewriter for Capture Analysis #78160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Apr 18, 2025
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace Microsoft.CodeAnalysis.CSharp
internal abstract class BoundTreeToDifferentEnclosingContextRewriter : BoundTreeRewriterWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator
{
// A mapping from every local variable to its replacement local variable. Local variables are replaced when
// their types change due to being inside of a generic method. Otherwise we reuse the original local (even
// their types change due to being inside of a generic method. Otherwise we may reuse the original local (even
// though its containing method is not correct because the code is moved into another method)
private readonly Dictionary<LocalSymbol, LocalSymbol> localMap = new Dictionary<LocalSymbol, LocalSymbol>();

Expand All @@ -30,6 +30,8 @@ internal abstract class BoundTreeToDifferentEnclosingContextRewriter : BoundTree

protected abstract MethodSymbol CurrentMethod { get; }

protected virtual bool AllowReuseOfLocalsWithIncorrectContainingSymbol => true;

public override BoundNode DefaultVisit(BoundNode node)
{
Debug.Fail($"Override the visitor for {node.Kind}");
Expand All @@ -55,7 +57,7 @@ protected virtual bool TryRewriteLocal(LocalSymbol local, [NotNullWhen(true)] ou
}

var newType = VisitType(local.Type);
if (TypeSymbol.Equals(newType, local.Type, TypeCompareKind.ConsiderEverything2))
if (AllowReuseOfLocalsWithIncorrectContainingSymbol && TypeSymbol.Equals(newType, local.Type, TypeCompareKind.ConsiderEverything2))
{
newLocal = local;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ internal sealed class ExtensionMethodBodyRewriter : BoundTreeToDifferentEnclosin

private RewrittenMethodSymbol _rewrittenContainingMethod;

/// <summary>
/// To allow regular capture analysis we do not want to reuse locals with an incorrect containing symbol
/// </summary>
protected override bool AllowReuseOfLocalsWithIncorrectContainingSymbol => false;

public ExtensionMethodBodyRewriter(MethodSymbol sourceMethod, SourceExtensionImplementationMethodSymbol implementationMethod)
{
Debug.Assert(sourceMethod is not null);
Expand Down
43 changes: 43 additions & 0 deletions src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,49 @@ public static class Extensions
Assert.Equal("Extensions.<>E__1<T>", symbol2.ToTestDisplayString());
}

[Fact]
[WorkItem("https://github.com/dotnet/roslyn/issues/78135")]
public void ExtensionWithCapturing()
{
var src = """
using System;
using System.Text;

var sb = new StringBuilder("Info: ");
Extensions.Inspect(sb);

public static class Extensions
{
extension(StringBuilder)
{
public static StringBuilder Inspect(StringBuilder sb)
{
var s = () =>
{
foreach (char c in sb.ToString())
{
Console.Write(c);
}
};
s();
return sb;
}
}
}
""";

var comp = CreateCompilation(src);
comp.VerifyEmitDiagnostics();

var tree = comp.SyntaxTrees[0];
var model = comp.GetSemanticModel(tree);
var extension1 = tree.GetRoot().DescendantNodes().OfType<ExtensionDeclarationSyntax>().First();
var symbol1 = model.GetDeclaredSymbol(extension1);
var sourceExtension1 = symbol1.GetSymbol<SourceNamedTypeSymbol>();
Assert.Equal("<>E__0", symbol1.MetadataName);
Assert.Equal("Extensions.<>E__0", symbol1.ToTestDisplayString());
}

[Fact]
public void ExtensionIndex_TwoExtensions_SameSignatures_02()
{
Expand Down
Loading