-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Changes from 3 commits
5943daf
462c0e2
952792a
a135d99
5ec929b
68d0b49
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1958,6 +1958,27 @@ internal static bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint | |
return false; | ||
} | ||
|
||
internal static uint GetBroadestValEscape(BoundTupleExpression expr, uint scopeOfTheContainingExpression) | ||
{ | ||
uint broadest = scopeOfTheContainingExpression; | ||
foreach (var element in expr.Arguments) | ||
{ | ||
uint valEscape; | ||
if (element.Kind == BoundKind.TupleLiteral) | ||
{ | ||
valEscape = GetBroadestValEscape((BoundTupleExpression)element, scopeOfTheContainingExpression); | ||
} | ||
else | ||
{ | ||
valEscape = GetValEscape(element, scopeOfTheContainingExpression); | ||
} | ||
|
||
broadest = Math.Min(broadest, valEscape); | ||
} | ||
|
||
return broadest; | ||
} | ||
|
||
/// <summary> | ||
/// Computes the widest scope depth to which the given expression can escape by value. | ||
/// | ||
|
@@ -1990,6 +2011,8 @@ internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainin | |
case BoundKind.DefaultExpression: | ||
case BoundKind.Parameter: | ||
case BoundKind.ThisReference: | ||
case BoundKind.TupleLiteral: | ||
case BoundKind.ConvertedTupleLiteral: | ||
// always returnable | ||
return Binder.ExternalScope; | ||
|
||
|
@@ -2004,6 +2027,9 @@ internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainin | |
// same as uninitialized local | ||
return Binder.ExternalScope; | ||
|
||
case BoundKind.DeconstructValuePlaceholder: | ||
return ((BoundDeconstructValuePlaceholder)expr).ValEscape; | ||
|
||
case BoundKind.Local: | ||
return ((BoundLocal)expr).LocalSymbol.ValEscapeScope; | ||
|
||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I corrected the comment just above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the scenario. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. In reply to: 141237397 [](ancestors = 141237397) |
||
|
||
|
@@ -2249,6 +2277,15 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint | |
// same as uninitialized local | ||
return true; | ||
|
||
case BoundKind.DeconstructValuePlaceholder: | ||
var placeholder = (BoundDeconstructValuePlaceholder)expr; | ||
if (placeholder.ValEscape > escapeTo) | ||
{ | ||
Error(diagnostics, ErrorCode.ERR_EscapeLocal, node, placeholder.Syntax); | ||
return false; | ||
} | ||
return true; | ||
|
||
case BoundKind.Local: | ||
var localSymbol = ((BoundLocal)expr).LocalSymbol; | ||
if (localSymbol.ValEscapeScope > escapeTo) | ||
|
@@ -2463,8 +2500,6 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint | |
// case BoundKind.NameOfOperator: | ||
// case BoundKind.InterpolatedString: | ||
// case BoundKind.StringInsert: | ||
// case BoundKind.TupleLiteral: | ||
// case BoundKind.ConvertedTupleLiteral: | ||
// case BoundKind.DynamicIndexerAccess: | ||
// case BoundKind.Lambda: | ||
// case BoundKind.DynamicObjectCreationExpression: | ||
|
@@ -2541,7 +2576,6 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint | |
// case BoundKind.DeclarationPattern: | ||
// case BoundKind.ConstantPattern: | ||
// case BoundKind.WildcardPattern: | ||
// case BoundKind.DeconstructValuePlaceholder: | ||
|
||
#endregion | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -113,10 +113,12 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia | |
bool resultIsUsed, | ||
DiagnosticBag diagnostics) | ||
{ | ||
uint rightEscape = GetValEscape(boundRHS, this.LocalScopeDepth); | ||
|
||
if ((object)boundRHS.Type == null || boundRHS.Type.IsErrorType()) | ||
{ | ||
// we could still not infer a type for the RHS | ||
FailRemainingInferences(checkedVariables, diagnostics); | ||
FailRemainingInferencesAndSetValEscape(checkedVariables, diagnostics, rightEscape); | ||
var voidType = GetSpecialType(SpecialType.System_Void, diagnostics, node); | ||
|
||
var type = boundRHS.Type ?? voidType; | ||
|
@@ -139,11 +141,14 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia | |
checkedVariables, | ||
out conversion); | ||
|
||
FailRemainingInferences(checkedVariables, diagnostics); | ||
FailRemainingInferencesAndSetValEscape(checkedVariables, diagnostics, rightEscape); | ||
|
||
var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: hasErrors || !resultIsUsed); | ||
TypeSymbol returnType = hasErrors ? CreateErrorType() : lhsTuple.Type; | ||
|
||
uint leftEscape = GetBroadestValEscape(lhsTuple, this.LocalScopeDepth); | ||
boundRHS = ValidateEscape(boundRHS, leftEscape, isByRef: false, diagnostics: diagnostics); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
||
var boundConversion = new BoundConversion( | ||
boundRHS.Syntax, | ||
boundRHS, | ||
|
@@ -248,7 +253,7 @@ private BoundExpression FixTupleLiteral(ArrayBuilder<DeconstructionVariable> che | |
else | ||
{ | ||
ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders; | ||
var inputPlaceholder = new BoundDeconstructValuePlaceholder(syntax, type); | ||
var inputPlaceholder = new BoundDeconstructValuePlaceholder(syntax, this.LocalScopeDepth, type); | ||
var deconstructInvocation = MakeDeconstructInvocationExpression(variables.Count, | ||
inputPlaceholder, rightSyntax, diagnostics, out outPlaceholders); | ||
|
||
|
@@ -345,24 +350,33 @@ private BoundExpression SetInferredType(BoundExpression expression, TypeSymbol t | |
|
||
/// <summary> | ||
/// Find any deconstruction locals that are still pending inference and fail their inference. | ||
/// Set the safe-to-escape scope for all deconstruction locals. | ||
/// </summary> | ||
private void FailRemainingInferences(ArrayBuilder<DeconstructionVariable> variables, DiagnosticBag diagnostics) | ||
private void FailRemainingInferencesAndSetValEscape(ArrayBuilder<DeconstructionVariable> variables, DiagnosticBag diagnostics, | ||
uint rhsValEscape) | ||
{ | ||
int count = variables.Count; | ||
for (int i = 0; i < count; i++) | ||
{ | ||
var variable = variables[i]; | ||
if (variable.HasNestedVariables) | ||
{ | ||
FailRemainingInferences(variable.NestedVariables, diagnostics); | ||
FailRemainingInferencesAndSetValEscape(variable.NestedVariables, diagnostics, rhsValEscape); | ||
} | ||
else | ||
{ | ||
switch (variable.Single.Kind) | ||
{ | ||
case BoundKind.Local: | ||
var local = (BoundLocal)variable.Single; | ||
if (local.IsDeclaration) | ||
{ | ||
((SourceLocalSymbol)local.LocalSymbol).SetValEscape(rhsValEscape); | ||
} | ||
break; | ||
case BoundKind.DeconstructionVariablePendingInference: | ||
BoundExpression local = ((DeconstructionVariablePendingInference)variable.Single).FailInference(this, diagnostics); | ||
variables[i] = new DeconstructionVariable(local, local.Syntax); | ||
BoundExpression errorLocal = ((DeconstructionVariablePendingInference)variable.Single).FailInference(this, diagnostics); | ||
variables[i] = new DeconstructionVariable(errorLocal, errorLocal.Syntax); | ||
break; | ||
case BoundKind.DiscardExpression: | ||
var pending = (BoundDiscardExpression)variable.Single; | ||
|
@@ -672,7 +686,7 @@ private static string ExtractDeconstructResultElementName(BoundExpression expres | |
out ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders, BoundExpression childNode) | ||
{ | ||
Error(diagnostics, ErrorCode.ERR_MissingDeconstruct, rightSyntax, receiver.Type, numParameters); | ||
outPlaceholders = default(ImmutableArray<BoundDeconstructValuePlaceholder>); | ||
outPlaceholders = default; | ||
|
||
return BadExpression(rightSyntax, childNode); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect this test to be part of the switch in
GetValEscape
. It seems out of place here. #ResolvedThere was a problem hiding this comment.
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)