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

Reuse previous lambda body binding if possible #43366

Merged
merged 2 commits into from
Apr 20, 2020
Merged

Conversation

cston
Copy link
Member

@cston cston commented Apr 15, 2020

Allow re-use of expression-bodied lambda bodies bound during return type inference. (Re-use was disabled in #37052.)

@cston cston requested a review from a team as a code owner April 15, 2020 06:26
@cston cston marked this pull request as draft April 15, 2020 06:27
@cston cston marked this pull request as ready for review April 16, 2020 17:04
@@ -3195,6 +3193,15 @@ public BoundBlock BindLambdaExpressionAsBlock(ExpressionSyntax body, DiagnosticB
return bodyBinder.CreateBlockFromExpression(body, bodyBinder.GetDeclaredLocalsForScope(body), refKind, expression, expressionSyntax, diagnostics);
}

public BoundBlock BindLambdaExpressionAsBlockContinued(ExpressionSyntax body, BoundExpression expression, DiagnosticBag diagnostics)
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BindLambdaExpressionAsBlockContinued [](start = 26, length = 36)

"Continued" doesn't really convey anything specific. Continued from where? Etc. Consider renaming with a more specific name, it feels like simply using CreateBlockFromExpression would work. #Closed

types.Add((returnStatement.ExpressionOpt, type));
}
}

HashSet<DiagnosticInfo> useSiteDiagnostics = null;
var bestType = CalculateReturnType(compilation, conversions, delegateType, types, isAsync, node, ref useSiteDiagnostics);
return new InferredLambdaReturnType(types.Count, hasReturnWithoutArgument, refKind, bestType, useSiteDiagnostics.AsImmutableOrEmpty());
types.Free();
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

types.Free(); [](start = 12, length = 13)

Were we leaking this builder before? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.


In reply to: 410970357 [](ancestors = 410970357)

@@ -927,7 +933,7 @@ BoundLambda rebind(BoundLambda lambda)
returnType = DelegateReturnTypeWithAnnotations(invokeMethod, out refKind);
if (!returnType.HasType || returnType.Type.ContainsTypeParameter())
{
var t = (inferredReturnType.HadExpressionlessReturn || inferredReturnType.NumExpressions == 0)
var t = !inferredReturnType.HasReturnValue
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!inferredReturnType.HasReturnValue [](start = 32, length = 34)

It feels like this refactoring is not semantically equivalent. It looks like before we could have HadExpressionlessReturn == true and inferredReturnType.NumExpressions > 0 at the same time. The orignal condition would be true. The new condition for the same scenario would be false. #Closed

if (statements.Length == 1 &&
statements[0] is BoundReturnStatement { RefKind: Microsoft.CodeAnalysis.RefKind.None, ExpressionOpt: BoundExpression expr })
{
return expr;
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return expr; [](start = 20, length = 12)

It is not clear why it is reusable? The name doesn't imply any restrictions on the input. I can imagine a situation when some conversions are have already been applied based on some context, therefore making the expression un-reusable. If the method is not responsible for enforcing reusability, then it should not be part of the name. This is just a helper that extracts return expression from a block with a single ref-none return. #Closed

return null;
}

protected override BoundBlock CreateBlockFromExpression(LambdaSymbol lambdaSymbol, Binder lambdaBodyBinder, BoundExpression expression, DiagnosticBag diagnostics)
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LambdaSymbol lambdaSymbol [](start = 64, length = 25)

It doesn't look like this parameter aver used, please remove. #Closed

var cacheKey = ReturnInferenceCacheKey.Create(delegateType, IsAsync);

// When binding for real (not for return inference), there is still
// a good chance that we could reuse a body of a lambda previous bound for
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a good chance that we could reuse a body of a lambda previous bound for [](start = 15, length = 71)

It doesn't look like we are reusing the body, we are reusing some portion of it. Consider reflecting this in the comment and also explaining why it is Ok to reuse that portion. #Closed

{
var statements = body.Statements;
if (statements.Length == 1 &&
statements[0] is BoundReturnStatement { RefKind: Microsoft.CodeAnalysis.RefKind.None, ExpressionOpt: BoundExpression expr })
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RefKind: Microsoft.CodeAnalysis.RefKind.None [](start = 60, length = 44)

It is not obvious why this condition is significant, consider adding a clarifying comment. #Closed

// return type inference.
if (_returnInferenceCache.TryGetValue(cacheKey, out BoundLambda returnInferenceLambda) &&
GetReusableLambdaExpressionBody(returnInferenceLambda.Body) is BoundExpression expression &&
(lambdaSymbol = returnInferenceLambda.Symbol).RefKind == refKind &&
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(lambdaSymbol = returnInferenceLambda.Symbol).RefKind == refKind [](start = 16, length = 64)

Should we also check that both are None as GetReusableLambdaExpressionBody checks below? #Closed


[ConditionalFactAttribute(typeof(IsRelease))]
[WorkItem(1083969, "https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1083969")]
public void NestedLambdas_02()
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NestedLambdas_02 [](start = 20, length = 16)

What exactly are we testing here? Were we unable to compile the code before? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're testing performance. On my machine, this test completes in a couple of seconds with this change and 15+ mins without.


In reply to: 410978595 [](ancestors = 410978595)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're testing performance. On my machine, this test completes in a couple of seconds with this change and 15+ mins without.

Consider adding a comment to make it easy to analyze failures.


In reply to: 411004957 [](ancestors = 411004957,410978595)

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Apr 19, 2020

Done with review pass (iteration 1) #Closed

RefKind refKind,
TypeWithAnnotations typeWithAnnotations,
ImmutableArray<DiagnosticInfo> useSiteDiagnostics)
{
NumExpressions = numExpressions;
HadExpressionlessReturn = hadExpressionlessReturn;
HasReturnValue = hasReturnValue;
Copy link
Contributor

@AlekseyTs AlekseyTs Apr 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HasReturnValue = hasReturnValue; [](start = 12, length = 32)

It looks like this refactoring is not related to primary goal of this PR, please exclude it from the PR. #Closed

@@ -467,6 +469,17 @@ internal UnboundLambdaState WithCaching(bool includeCache)
public abstract RefKind RefKind(int index);
protected abstract BoundBlock BindLambdaBody(LambdaSymbol lambdaSymbol, Binder lambdaBodyBinder, DiagnosticBag diagnostics);

/// <summary>
/// Return the bound expression if the lambda is has an expression body and can be reused easily.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is has [](start = 54, length = 6)

Typo?

Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (iteration 2)

Copy link
Member

@gafter gafter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@cston cston merged commit 278435e into dotnet:master Apr 20, 2020
@ghost ghost added this to the Next milestone Apr 20, 2020
@cston cston deleted the lambda-reuse branch April 20, 2020 04:11
@cston cston modified the milestones: Next, 16.7.P1 Apr 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants