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

Escape rules for deconstruction and foreach variables #22354

Merged
merged 6 commits into from
Sep 29, 2017

Conversation

jcouv
Copy link
Member

@jcouv jcouv commented Sep 26, 2017

Customer scenario
Use ref-like types (such as Span) in a deconstruction or foreach loop. The assignments implicit in those operations need to be tracked to avoid holding on to references past their scope.

Bugs this fixes:
Fixes #22338

Details of the fix

  1. Variables introduced by deconstructions and foreach (regular or deconstruction form) need to track their safe-to-escape scope, as dictated by the right-hand-side of a deconstruction and the foreach collection, respectively.
  2. Expressions in the left-hand-side of a deconstruction need to abide by safe-to-escape rules for assignment: their safe-to-escape scopes should be narrower than the value they will receive from the right-hand-side.

Workarounds, if any
No workaround. This is a safety rule the compiler should enforce.

Risk
Performance impact
Low. This is mostly adding a check to binding deconstructions and foreach.

Is this a regression from a previous update?
No. This is part of a C# 7.2 feature (part of 15.5 release).

@jcouv jcouv added this to the 15.5 milestone Sep 26, 2017
@jcouv jcouv self-assigned this Sep 26, 2017
@jcouv
Copy link
Member Author

jcouv commented Sep 26, 2017

@gafter for review. Thanks

foreach (var element in expr.Arguments)
{
uint valEscape;
if (element.Kind == BoundKind.TupleLiteral)
Copy link
Member

@gafter gafter Sep 26, 2017

Choose a reason for hiding this comment

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

if [](start = 16, length = 2)

I would expect this test to be part of the switch in GetValEscape. It seems out of place here. #Resolved

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I see, in GetValEscape we would be using Math.Max.


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

@@ -2237,6 +2263,8 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint
case BoundKind.DefaultExpression:
case BoundKind.Parameter:
case BoundKind.ThisReference:
case BoundKind.TupleLiteral:
case BoundKind.ConvertedTupleLiteral:
// always returnable
return true;
Copy link
Member

@gafter gafter Sep 26, 2017

Choose a reason for hiding this comment

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

true [](start = 27, length = 4)

I don't think this is correct for a tuple literal. For example, I could write

(spanA, someString) = (spanB, null);

This is all valid in the type system even if spanA and spanB are ref structs (because the right-hand-side has no type), but the safe-to-escape value computed from the right-hand-side is from the safe-to-escape of the variable tupleB, and the whole thing is only valid if the assignment spanA = spanB would be valid.

Copy link
Member

Choose a reason for hiding this comment

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

I corrected the comment just above.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the scenario.
I don't agree with your expectation though. The tuple on the right does receive a type, by inferring a merged type from both sides. In this case, the RHS will be made into a (Span, string) which is illegal.
So (spanA, someString) = (spanB, null); should produce an constraint error (Span cannot be used as a type argument).

Copy link
Member

Choose a reason for hiding this comment

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

In current circumstances a tuple literal cannot not contain ref-like elements (That could be considered a bug, but it is what it is). Considering that, returning val-escape being External seems to be valid.
Once we allow tuples to contain ref-likes, we will need to revisit this place. For now it seems valid assumption,


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

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.

I think there is a remaining issue for code such as (spanA, someString) = (spanB, null);, which it appears this code would permit even if spanA = spanB would be rejected.

@jcouv jcouv force-pushed the value-escape branch 2 times, most recently from 22686fb to b55f46e Compare September 27, 2017 20:05
@jcouv
Copy link
Member Author

jcouv commented Sep 27, 2017

Filed feature request to allow deconstruction without requiring ValueTuple type, even when the right-hand-side is a tuple literal (#22382).
When we get to that, we'll have to handle safe-to-escape rules in more details (doing element-wise checks). I took a note in the issue to remind ourselves.

@jcouv
Copy link
Member Author

jcouv commented Sep 27, 2017

@VSadov @dotnet/roslyn-compiler for review. Thanks

var tree = compilation.SyntaxTrees[0];
var decl = (ArgumentSyntax)tree.GetCompilationUnitRoot().DescendantNodes().Last(n => n.IsKind(SyntaxKind.Argument));
var model = compilation.GetSemanticModel(tree);
Assert.Null(model.GetDeclaredSymbol(decl));
Copy link
Member Author

Choose a reason for hiding this comment

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

📝 There is a slight regression of the semantic model here. But (1) I don't think it is very significant, and (2) I think it is worthwhile because the root cause is a change to avoid many duplicate diagnostics.

TypeSymbol returnType = hasErrors ? CreateErrorType() : lhsTuple.Type;

uint leftEscape = GetBroadestValEscape(lhsTuple, this.LocalScopeDepth);
boundRHS = ValidateEscape(boundRHS, leftEscape, isByRef: false, diagnostics: diagnostics);
Copy link
Member

Choose a reason for hiding this comment

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

In the future we may want to do this pair-wise each value to each assignment target.
For now, since in all reachable cases the assignment values will have the same escape which is equal to escape of RHS, this is sufficient.

Copy link
Member

@VSadov VSadov left a comment

Choose a reason for hiding this comment

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

:shipit:

@jcouv
Copy link
Member Author

jcouv commented Sep 28, 2017

@gafter Please review when you have the chance.
As we discussed I added an implementation of val escape for tuples.
Also, I realized after our discussion that I should always compute the merged tuple type for a deconstruction (even when I will discard the diagnostics that produces). The latest commit fixes that.

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:

@jcouv
Copy link
Member Author

jcouv commented Sep 29, 2017

test windows_debug_unit32_prtest please

1 similar comment
@jcouv
Copy link
Member Author

jcouv commented Sep 29, 2017

test windows_debug_unit32_prtest please

@jcouv jcouv merged commit 3d3b524 into dotnet:master Sep 29, 2017
@jcouv jcouv deleted the value-escape branch September 29, 2017 20:52
333fred added a commit to 333fred/roslyn that referenced this pull request Sep 29, 2017
* dotnet/master:
  Escape rules for deconstruction and foreach variables (dotnet#22354)
  Address review feedback
  Address PR feedback
  Add IOperation unit tests for try/catch statements and make the APIs public again
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.

Implement safe-to-escape rules for foreach and deconstructions
4 participants