Skip to content
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

Avoid binding lambdas unnecessarily in some error scenarios #59733

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
19 changes: 18 additions & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1889,6 +1889,22 @@ private ImmutableArray<BoundExpression> BuildArgumentsForErrorRecovery(AnalyzedA
return result;
}

private const int MaxLambdaExpressionDepthForErrorRecovery = 2;

private static int NestedLambdaExpressionDepth(SyntaxNode syntax)
{
int n = 0;
while (syntax is { })
{
if (syntax.Kind() is SyntaxKind.SimpleLambdaExpression or SyntaxKind.ParenthesizedLambdaExpression or SyntaxKind.AnonymousMethodExpression)
{
n++;
}
syntax = syntax.Parent;
}
return n;
}

private ImmutableArray<BoundExpression> BuildArgumentsForErrorRecovery(AnalyzedArguments analyzedArguments, IEnumerable<ImmutableArray<ParameterSymbol>> parameterListList)
{
int argumentCount = analyzedArguments.Arguments.Count;
Expand All @@ -1915,7 +1931,8 @@ unboundArgument.FunctionType is { } functionType &&
// Just assume we're not in an expression tree for the purposes of error recovery.
_ = unboundArgument.Bind(delegateType, isExpressionTree: false);
}
else
else if (NestedLambdaExpressionDepth(unboundArgument.Syntax) <= MaxLambdaExpressionDepthForErrorRecovery &&
!unboundArgument.HasBoundForErrorRecovery)
{
// bind the argument against each applicable parameter
foreach (var parameterList in parameterListList)
Expand Down
11 changes: 11 additions & 0 deletions src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,8 @@ internal UnboundLambda WithNoCache()
public BoundLambda Bind(NamedTypeSymbol delegateType, bool isExpressionTree)
=> SuppressIfNeeded(Data.Bind(delegateType, isExpressionTree));

public bool HasBoundForErrorRecovery => Data.HasBoundForErrorRecovery;

public BoundLambda BindForErrorRecovery()
=> SuppressIfNeeded(Data.BindForErrorRecovery());

Expand Down Expand Up @@ -494,6 +496,8 @@ internal sealed class LambdaBindingData
/// Number of lambdas bound.
/// </summary>
internal int LambdaBindingCount;

internal int UnboundLambdaStateCount;
}

internal abstract class UnboundLambdaState
Expand All @@ -518,6 +522,11 @@ public UnboundLambdaState(Binder binder, bool includeCache)
Debug.Assert(binder != null);
Debug.Assert(binder.ContainingMemberOrLambda != null);

if (binder.Compilation.TestOnlyCompilationData is LambdaBindingData data)
{
Interlocked.Increment(ref data.UnboundLambdaStateCount);
}

if (includeCache)
{
_bindingCache = ImmutableDictionary<(NamedTypeSymbol Type, bool IsExpressionLambda), BoundLambda>.Empty.WithComparers(BindingCacheComparer.Instance);
Expand Down Expand Up @@ -1118,6 +1127,8 @@ public virtual Binder GetWithParametersBinder(LambdaSymbol lambdaSymbol, Binder
return new WithLambdaParametersBinder(lambdaSymbol, binder);
}

public bool HasBoundForErrorRecovery => _errorBinding is { };

// UNDONE: [MattWar]
// UNDONE: Here we enable the consumer of an unbound lambda that could not be
// UNDONE: successfully converted to a best bound lambda to do error recovery
Expand Down
30 changes: 30 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2615,6 +2615,36 @@ static void Mc() { }
}
}

[Fact]
public void TestLambdaWithError20()
{
var source =
@"using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
M(string.Empty, y, (x, y) => x.ToString());
}
static void M<T, U>(T t, U u, Expression<Action<T, U, int>> action) { }
}
";
var compilation = CreateCompilationWithMscorlib40AndSystemCore(source);
var tree = compilation.SyntaxTrees[0];
var sm = compilation.GetSemanticModel(tree);
var lambda = tree.GetRoot().DescendantNodes().OfType<LambdaExpressionSyntax>().Single();
var reference = lambda.Body.DescendantNodesAndSelf().OfType<IdentifierNameSyntax>().First();
Assert.Equal("x", reference.ToString());
var typeInfo = sm.GetTypeInfo(reference);
// The inferred parameter type is unknown, unlike similar tests in TestLambdaWithError19,
// because BindInvocationExpressionContinued is calling unboundLambda.BindForErrorRecovery(),
// which binds the lambda without a delegate type rather than attempting to infer a delegate
// type from the inapplicable method, even though the argument error is from 'y' rather
// than from the lambda argument.
Assert.Equal(TypeKind.Error, typeInfo.Type.TypeKind);
}

// See MaxParameterListsForErrorRecovery.
[Fact]
public void BuildArgumentsForErrorRecovery_ManyOverloads()
Expand Down
Loading
Loading