-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Change local function definite assignment #23749
Changes from 2 commits
d2e208d
e3cca99
ab07b36
6d3694c
d3669d7
162e52e
7e9381e
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 |
---|---|---|
|
@@ -124,17 +124,29 @@ public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatemen | |
|
||
var oldPending = SavePending(); // we do not support branches into a lambda | ||
|
||
// Local functions don't affect outer state and are analyzed | ||
// with everything unassigned and reachable | ||
// SPEC: The entry point to a local function is always reachable. | ||
// Captured variables are assigned if they are assigned on all | ||
// branches into the local function. | ||
|
||
var savedState = this.State; | ||
this.State = this.ReachableState(); | ||
|
||
var usages = GetOrCreateLocalFuncUsages(localFuncSymbol); | ||
var oldReads = usages.ReadVars; | ||
usages.ReadVars = BitVector.Empty; | ||
|
||
if (!localFunc.WasCompilerGenerated) EnterParameters(localFuncSymbol.Parameters); | ||
|
||
// Captured variables are definitely assigned if they are assigned on | ||
// all branches into the local function, so we store all reads from | ||
// possibly unassigned captured variables and later report definite | ||
// assignment errors if any of the captured variables is not assigned | ||
// on a particular branch. | ||
// Assignments to captured variables are also recorded, as a local function | ||
// definitely assigns captured variables on a call to a local function | ||
// if that variable is definitely assigned at all branches out of the | ||
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.
"branches out of" should be "returns from". #Resolved 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. Yield return, await, and the implicit return before the first statement in an iterator all count -- would you classify those as return statements? |
||
// local function | ||
|
||
var usages = GetOrCreateLocalFuncUsages(localFuncSymbol); | ||
var oldReads = usages.ReadVars.Clone(); | ||
usages.ReadVars.Clear(); | ||
|
||
var oldPending2 = SavePending(); | ||
|
||
// If this is an iterator, there's an implicit branch before the first statement | ||
|
@@ -158,6 +170,7 @@ public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatemen | |
|
||
LeaveParameters(localFuncSymbol.Parameters, localFunc.Syntax, location); | ||
|
||
// Intersect the state of all branches out of the local function | ||
LocalState stateAtReturn = this.State; | ||
foreach (PendingBranch pending in pendingReturns) | ||
{ | ||
|
@@ -173,13 +186,20 @@ public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatemen | |
IntersectWith(ref stateAtReturn, ref this.State); | ||
} | ||
|
||
// Check for changes to the read and write sets | ||
// Check for changes to the possibly unassigned and assigned sets | ||
// of captured variables | ||
if (RecordChangedVars(ref usages.WrittenVars, | ||
ref stateAtReturn, | ||
ref oldReads, | ||
ref usages.ReadVars) && | ||
usages.LocalFuncVisited) | ||
{ | ||
// If the sets have changed and we already used the results | ||
// of this local function in another computation, the previous | ||
// calculations may be invalid. We need to analyze until we | ||
// reach a fixed-point. The previous writes are always valid, | ||
// so they are stored in usages.WrittenVars, while the reads | ||
// may be invalidated by new writes, so we throw the results out. | ||
stateChangedAfterUse = true; | ||
usages.LocalFuncVisited = false; | ||
} | ||
|
@@ -261,8 +281,7 @@ private BitVector GetCapturedBitmask(ref BitVector state) | |
return mask; | ||
} | ||
|
||
private bool IsCapturedInLocalFunction(int slot, | ||
ParameterSymbol rangeVariableUnderlyingParameter = null) | ||
private bool IsCapturedInLocalFunction(int slot) | ||
{ | ||
if (slot <= 0) return false; | ||
|
||
|
@@ -276,8 +295,8 @@ private bool IsCapturedInLocalFunction(int slot, | |
// container is higher in the tree than the nearest | ||
// local function | ||
var nearestLocalFunc = GetNearestLocalFunctionOpt(currentMethodOrLambda); | ||
return (object)nearestLocalFunc != null && | ||
IsCaptured(rootSymbol, nearestLocalFunc, rangeVariableUnderlyingParameter); | ||
|
||
return !(nearestLocalFunc is null) && IsCaptured(rootSymbol, nearestLocalFunc); | ||
} | ||
|
||
private LocalFuncUsages GetOrCreateLocalFuncUsages(LocalFunctionSymbol localFunc) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -394,38 +394,52 @@ protected void Analyze(ref bool badRegion, DiagnosticBag diagnostics) | |
/// <param name="rangeVariableUnderlyingParameter">If variable.Kind is RangeVariable, its underlying lambda parameter. Else null.</param> | ||
private void CheckCaptured(Symbol variable, ParameterSymbol rangeVariableUnderlyingParameter = null) | ||
{ | ||
if (IsCaptured(variable, | ||
currentMethodOrLambda, | ||
rangeVariableUnderlyingParameter)) | ||
if (IsCaptured(rangeVariableUnderlyingParameter ?? variable, currentMethodOrLambda)) | ||
{ | ||
NoteCaptured(variable); | ||
} | ||
} | ||
|
||
private static bool IsCaptured(Symbol variable, | ||
MethodSymbol containingMethodOrLambda, | ||
ParameterSymbol rangeVariableUnderlyingParameter) | ||
private static bool IsCaptured(Symbol variable, MethodSymbol containingMethodOrLambda) | ||
{ | ||
switch (variable.Kind) | ||
{ | ||
case SymbolKind.Field: | ||
case SymbolKind.Property: | ||
case SymbolKind.Event: | ||
// Range variables are not captured, but their underlying parameters | ||
// may be. If this is a range underlying parameter it will be a | ||
// ParameterSymbol, not a RangeVariableSymbol. | ||
case SymbolKind.RangeVariable: | ||
return false; | ||
|
||
case SymbolKind.Local: | ||
if (((LocalSymbol)variable).IsConst) break; | ||
goto case SymbolKind.Parameter; | ||
case SymbolKind.Parameter: | ||
if (containingMethodOrLambda != variable.ContainingSymbol) | ||
if (((LocalSymbol)variable).IsConst) | ||
{ | ||
return true; | ||
return false; | ||
} | ||
break; | ||
case SymbolKind.RangeVariable: | ||
if (rangeVariableUnderlyingParameter != null && | ||
containingMethodOrLambda != rangeVariableUnderlyingParameter.ContainingSymbol) | ||
{ | ||
return true; | ||
} | ||
|
||
case SymbolKind.Parameter: | ||
break; | ||
|
||
default: | ||
throw ExceptionUtilities.UnexpectedValue(variable.Kind); | ||
} | ||
|
||
// Walk up the containing symbols until we find the target function, in which | ||
// case the variable is not captured by the target function, or null, in which | ||
// case it is. | ||
var currentFunction = variable.ContainingSymbol; | ||
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. This could be for (var currentFunction = variable.ContainingSymbol; currentFunction != null; currentFunction = currentFunction.ContainingSymbol)
{
if (currentFunction == containingMethodOrLambda)
{
return false;
}
} |
||
while (currentFunction != null) | ||
{ | ||
if (currentFunction == containingMethodOrLambda) | ||
{ | ||
return false; | ||
} | ||
currentFunction = currentFunction.ContainingSymbol; | ||
} | ||
return false; | ||
return true; | ||
} | ||
|
||
/// <summary> | ||
|
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.
"assigned" should be "definitely assigned" (twice). #Resolved