-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Set val escape on expression variables #64414
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
Changes from all commits
f876d78
4eaaf44
8bb4344
8cfaf7e
d89b692
7aed7a3
da646ed
04437a5
451bad6
69f28be
f667de7
fe6f5cc
d00a579
fd6d734
1c01602
ad519df
19a09e8
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 |
|---|---|---|
|
|
@@ -1943,7 +1943,11 @@ private static void GetInvocationArgumentsForEscape( | |
| parameters[argsToParamsOpt.IsDefault ? argIndex : argsToParamsOpt[argIndex]] : | ||
| null; | ||
|
|
||
| if (mixableArguments is not null && isMixableParameter(parameter)) | ||
| if (mixableArguments is not null | ||
| && isMixableParameter(parameter) | ||
| // assume any expression variable is a valid mixing destination, | ||
| // since we will infer a legal val-escape for it (if it doesn't already have a narrower one). | ||
| && isMixableArgument(argument)) | ||
| { | ||
| mixableArguments.Add(new MixableDestination(parameter, argument)); | ||
| } | ||
|
|
@@ -1968,6 +1972,9 @@ parameter is not null && | |
| parameter.Type.IsRefLikeType && | ||
| parameter.RefKind.IsWritableReference(); | ||
|
|
||
| static bool isMixableArgument(BoundExpression argument) => | ||
| argument is not (BoundDeconstructValuePlaceholder or BoundLocal { DeclarationKind: not BoundLocalDeclarationKind.None }); | ||
|
|
||
| static EscapeArgument getReceiver(Symbol symbol, BoundExpression receiver) | ||
| { | ||
| if (symbol is FunctionPointerMethodSymbol) | ||
|
|
@@ -2195,6 +2202,30 @@ private bool UseUpdatedEscapeRulesForInvocation(Symbol symbol) | |
| return method?.UseUpdatedEscapeRules == true; | ||
| } | ||
|
|
||
| private static bool ShouldInferDeclarationExpressionValEscape(BoundExpression argument, [NotNullWhen(true)] out SourceLocalSymbol? localSymbol) | ||
| { | ||
| var symbol = argument switch | ||
| { | ||
| BoundDeconstructValuePlaceholder p => p.ExpressionSymbol, | ||
| BoundLocal { DeclarationKind: not BoundLocalDeclarationKind.None } l => l.ExpressionSymbol, | ||
| _ => null | ||
| }; | ||
| if (symbol is SourceLocalSymbol { ValEscapeScope: CallingMethodScope } local) | ||
| { | ||
| localSymbol = local; | ||
| return true; | ||
| } | ||
| else | ||
| { | ||
| // No need to infer a val escape for a global variable. | ||
| // These are only used in top-level statements in scripting mode, | ||
| // and since they are class fields, their scope is always CallingMethod. | ||
| Debug.Assert(symbol is null or SourceLocalSymbol or GlobalExpressionVariable); | ||
| localSymbol = null; | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Validates whether the invocation is valid per no-mixing rules. | ||
| /// Returns <see langword="false"/> when it is not valid and produces diagnostics (possibly more than one recursively) that helps to figure the reason. | ||
|
|
@@ -2239,7 +2270,7 @@ private bool CheckInvocationArgMixing( | |
| var escapeArguments = ArrayBuilder<EscapeArgument>.GetInstance(); | ||
| GetInvocationArgumentsForEscape( | ||
| symbol, | ||
| receiver: null, // receiver handled explicitly below | ||
| receiverOpt, | ||
| parameters, | ||
| argsOpt, | ||
| argRefKindsOpt: default, | ||
|
|
@@ -2252,43 +2283,51 @@ private bool CheckInvocationArgMixing( | |
| { | ||
| foreach (var (_, argument, refKind) in escapeArguments) | ||
| { | ||
| if (ShouldInferDeclarationExpressionValEscape(argument, out _)) | ||
|
Member
Author
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 think we also wanted to change this to use 'isMixableArgument'. I think the behavior/factoring for the current release is going to be fine, we just want to make sure to iron these kinds of things out when we refactor in 17.5. |
||
| { | ||
| // assume any expression variable is a valid mixing destination, | ||
| // since we will infer a legal val-escape for it (if it doesn't already have a narrower one). | ||
| continue; | ||
| } | ||
|
|
||
| if (refKind.IsWritableReference() && argument.Type?.IsRefLikeType == true) | ||
| { | ||
| escapeTo = Math.Min(escapeTo, GetValEscape(argument, scopeOfTheContainingExpression)); | ||
| } | ||
| } | ||
|
|
||
| if (escapeTo == scopeOfTheContainingExpression) | ||
| { | ||
| // cannot fail. common case. | ||
| return true; | ||
| } | ||
| var hasMixingError = false; | ||
|
|
||
| // track the widest scope that arguments could safely escape to. | ||
| // use this scope as the inferred STE of declaration expressions. | ||
| var inferredDestinationValEscape = CallingMethodScope; | ||
| foreach (var (parameter, argument, _) in escapeArguments) | ||
| { | ||
| var valid = CheckValEscape(argument.Syntax, argument, scopeOfTheContainingExpression, escapeTo, false, diagnostics); | ||
|
|
||
| if (!valid) | ||
| // in the old rules, we assume that refs cannot escape into ref struct variables. | ||
| // e.g. in `dest = M(ref arg)`, we assume `ref arg` will not escape into `dest`, but `arg` might. | ||
| inferredDestinationValEscape = Math.Max(inferredDestinationValEscape, GetValEscape(argument, scopeOfTheContainingExpression)); | ||
| if (!hasMixingError && !CheckValEscape(argument.Syntax, argument, scopeOfTheContainingExpression, escapeTo, false, diagnostics)) | ||
| { | ||
| string parameterName = GetInvocationParameterName(parameter); | ||
| Error(diagnostics, ErrorCode.ERR_CallArgMixing, syntax, symbol, parameterName); | ||
| return false; | ||
| hasMixingError = true; | ||
| } | ||
| } | ||
|
|
||
| foreach (var (_, argument, _) in escapeArguments) | ||
| { | ||
| if (ShouldInferDeclarationExpressionValEscape(argument, out var localSymbol)) | ||
| { | ||
| localSymbol.SetValEscape(inferredDestinationValEscape); | ||
| } | ||
| } | ||
|
Comment on lines
+2317
to
2323
Member
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 code is the same between new and updated rules. Should it be factored otu?
Member
Author
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 think it is just beneath the bar of factoring out. |
||
|
|
||
| return !hasMixingError; | ||
| } | ||
| finally | ||
| { | ||
| escapeArguments.Free(); | ||
| } | ||
|
|
||
| // check val escape of receiver if ref-like | ||
| if (receiverOpt?.Type?.IsRefLikeType == true) | ||
| { | ||
| // Should we also report ErrorCode.ERR_CallArgMixing if CheckValEscape() fails? | ||
| return CheckValEscape(receiverOpt.Syntax, receiverOpt, scopeOfTheContainingExpression, escapeTo, false, diagnostics); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| private bool CheckInvocationArgMixingWithUpdatedRules( | ||
|
|
@@ -2352,9 +2391,32 @@ private bool CheckInvocationArgMixingWithUpdatedRules( | |
| } | ||
| } | ||
|
|
||
| inferDeclarationExpressionValEscape(); | ||
|
|
||
| mixableArguments.Free(); | ||
| escapeValues.Free(); | ||
| return valid; | ||
|
|
||
| void inferDeclarationExpressionValEscape() | ||
| { | ||
| // find the widest scope that arguments could safely escape to. | ||
| // use this scope as the inferred STE of declaration expressions. | ||
| var inferredDestinationValEscape = CallingMethodScope; | ||
| foreach (var (_, fromArg, _, isRefEscape) in escapeValues) | ||
| { | ||
| inferredDestinationValEscape = Math.Max(inferredDestinationValEscape, isRefEscape | ||
| ? GetRefEscape(fromArg, scopeOfTheContainingExpression) | ||
| : GetValEscape(fromArg, scopeOfTheContainingExpression)); | ||
| } | ||
|
|
||
| foreach (var (_, fromArg, _, _) in escapeValues) | ||
| { | ||
| if (ShouldInferDeclarationExpressionValEscape(fromArg, out var localSymbol)) | ||
| { | ||
| localSymbol.SetValEscape(inferredDestinationValEscape); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private static bool IsReceiverRefReadOnly(Symbol methodOrPropertySymbol) => methodOrPropertySymbol switch | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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.
note that we really would prefer to use a sentinel ValEscapeScope in cases like these (e.g. with value
1 << 32). I attempted to do this but it started to really churn and wanted to be expedient for the purposes of getting this fix in.