-
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
Support for ref-readonly locals #22269
Conversation
Ref-readonly locals are mostly identical to ref-readonly parameters. The most important difference is that when possibly mutating methods are called on ref-readonly locals of struct type, a proper temporary is created before calling the method.
83b829e
to
cb6f875
Compare
appoved pending reviews |
ping @VSadov @OmarTawfik for reviews |
/// </summary> | ||
private LocalDefinition EmitDupAddress(BoundDup dup, AddressKind addressKind) | ||
{ | ||
if (!HasHome(dup, addressKind != AddressKind.ReadOnly)) |
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.
Consider using a named argument for the second parm here. Makes it more readable.
var local = ((BoundLocal)expression).LocalSymbol; | ||
return !IsStackLocal(local) || local.RefKind != RefKind.None; | ||
return !((IsStackLocal(local) && local.RefKind == RefKind.None) || | ||
(needWriteable && local.RefKind == RefKind.RefReadOnly)); |
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.
Is needWritable protecting the local or the value?
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.
The local. It's specifying that you must be able to write to the local pointed to by the address.
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.
Then why is the last condition only checking ref readonly
In reply to: 140599487 [](ancestors = 140599487)
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.
@@ -268,7 +268,7 @@ internal virtual bool IsWritable | |||
case LocalDeclarationKind.UsingVariable: | |||
return false; | |||
default: | |||
return true; | |||
return RefKind != RefKind.RefReadOnly; |
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.
The local is still assignable. Is that not what is being tracked here?
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.
So what do you expect to happen with the following code?
int x = 0;
ref readonly int xr = ref x;
xr++;
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.
// (11,25): error CS1510: A ref or out value must be an assignable variable | ||
// ref int z = ref x; | ||
Diagnostic(ErrorCode.ERR_RefLvalueExpected, "x").WithLocation(11, 25) | ||
); |
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.
Need a test that essentially does the following
ref readonly int x = ref 1;
This is legal when done as an argument to a ref readonly parameter. Need to be clear if this is okay / not okay when done in this context.
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.
This is prohibited for now. I'll add test. It does seem a little annoying that this is prohibited for locals but allowed for calls. We may want to relax that restriction and auto-spill like we do for arguments, but I'm not convinced that needs to be blocking for this PR.
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.
Also:
ref readonly object x = ref someStringLocal; // we will not allow conversions, at least for now
In reply to: 140600354 [](ancestors = 140600354)
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.
LGTM
with some test suggestions
Do we have a test to make sure this is not enabled on 7.1 ? In reply to: 331557740 [](ancestors = 331557740) |
@@ -234,6 +240,21 @@ private void EmitLocalAddress(BoundLocal localAccess) | |||
{ | |||
_builder.EmitLocalAddress(GetLocal(localAccess)); | |||
} | |||
return null; |
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.
nit: new line
@@ -971,7 +971,23 @@ private static bool IsIndirectAssignment(BoundAssignmentOperator node) | |||
{ | |||
var lhs = node.Left; | |||
|
|||
Debug.Assert(node.RefKind == RefKind.None || (lhs as BoundLocal)?.LocalSymbol.RefKind == RefKind.Ref, | |||
bool IsAssignable(RefKind lhsKind, RefKind rhsKind) |
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.
IsAssignable [](start = 17, length = 12)
if this is only for asserting, I'd suggest inlining into the Assert call and simply returning false otherwise. Throwing here will not trigger the Debug window while debugging, and will route it to the VS fault handler.
@@ -268,7 +268,7 @@ internal virtual bool IsWritable | |||
case LocalDeclarationKind.UsingVariable: | |||
return false; | |||
default: | |||
return true; | |||
return RefKind != RefKind.RefReadOnly; |
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.
can we explicitly state the true cases, and throw otherwise?
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 don't understand why you would want to ever throw here. Ref
variables are meaningful, ref-readonly
variables are meaningful, and "non-ref" variables are meaningful.
); | ||
} | ||
|
||
[Fact] | ||
public void RefLocalMissingInitializer() |
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'dd suggest adding a test for:
// In a ref readonly method
ref readonly int x = ref SomeField;
return ref x;
```
@@ -138,9 +138,6 @@ static void Use<T>(T dummy) | |||
"; | |||
var comp = CreateCompilationWithMscorlib45(text, new[] { ValueTupleRef, SystemRuntimeFacadeRef }); | |||
comp.VerifyDiagnostics( |
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.
in that case, we should remove the first line of the method. it is covered by others, and it is not proving the "unexpected bind time" here.
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.
In general, it's not a good idea to modify regression tests, even if they're obsolete. Modifying the baseline is fine, but it's good to keep the tests the same if only to prove that there are no backwards-incompatible changes.
Console.WriteLine(rs.X); | ||
rs.AddOne(); | ||
rs.AddOne(); | ||
rs.AddOne(); |
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.
rs.AddOne(); [](start = 8, length = 12)
not sure I understand that test. why calling it repeatedly afterwards?
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.
To make sure that we're reusing the same slot, instead of allocating multiple slots for the temp. This is exercising the optimizer.
IL_0052: call ""void S.AddOne()"" | ||
IL_0057: ret | ||
}"); | ||
// This should generate similar IL to the previous |
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.
// This should generate similar IL to the previous [](start = 12, length = 50)
why is this grouped into one test? consider separating them.
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.
This is a relatively unique IL pattern so Vlad and I thought it would be a good idea to prove you can reach this IL pattern without going through ref-readonly. The idea is to compare the IL generated for the optimizer spilling the temp to the IL generated for C# code manually spilling.
Console.WriteLine(temp.X); | ||
temp = sr; | ||
temp.AddOne(); | ||
Console.WriteLine(temp.X); |
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.
Console.WriteLine(temp.X); [](start = 8, length = 26)
should we print s's value afterwards?
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 think the IL is sufficient to prove that s
remains unmodified. As noted in the comment, this test is mostly important for verifying IL consistency.
Should we add |
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.
Done. left a few comments.
@OmarTawfik Regarding putting readonly in the completion list -- it was offered for me when I manually tested typing, so I don't think we need to make any changes there. |
@agocke I suggest then a test in |
@OmarTawfik Sounds good, I'll follow up with a bunch of IDE regression tests in a later PR. |
* dotnet/master: (351 commits) Avoid scheduling an unnecessary dispose task for EmptyAsyncToken Support for ref-readonly locals (dotnet#22269) ported tests from dotnet#21263 (dotnet#22296) NormalizeWhitespace should not add a space in nullable type (dotnet#22234) Fix the work item number associated with the unit test Add unit test Terminate a sentence Add instructions for upgrading PowerShell Unsafe local should require /unsafe flag on compilation (dotnet#22268) CR feedback Not poisoning restricted types known to be stack only. Typo Error messsage should mention C# 7.0 not C# 7 (dotnet#22255) Package descriptions for CSharp and VB should include summary blurb (dotnet#22166) Fix a trivial issue in how IsLifted is calculated for C# compound assignment operator Do not zero out "With" statement target expression locals referenced within a lambda. (dotnet#22223) Fix unit test failure from merge Mark "private protected", "ref readonly" and other C# 7.2 features as merged (dotnet#22209) Add unit tests for IAwaitExpression and make the API public again more test fixes due to merge conflicts ...
* dotnet/master: (309 commits) Avoid scheduling an unnecessary dispose task for EmptyAsyncToken Support for ref-readonly locals (dotnet#22269) ported tests from dotnet#21263 (dotnet#22296) NormalizeWhitespace should not add a space in nullable type (dotnet#22234) Fix the work item number associated with the unit test Add unit test Terminate a sentence Add instructions for upgrading PowerShell Unsafe local should require /unsafe flag on compilation (dotnet#22268) CR feedback Not poisoning restricted types known to be stack only. Typo Error messsage should mention C# 7.0 not C# 7 (dotnet#22255) Package descriptions for CSharp and VB should include summary blurb (dotnet#22166) Fix a trivial issue in how IsLifted is calculated for C# compound assignment operator Do not zero out "With" statement target expression locals referenced within a lambda. (dotnet#22223) Fix unit test failure from merge Mark "private protected", "ref readonly" and other C# 7.2 features as merged (dotnet#22209) Add unit tests for IAwaitExpression and make the API public again more test fixes due to merge conflicts ...
Yey! Was just about to raise issue for this - wasn't sure how you were meant to use a |
Customer scenario
New feature: ref-readonly local variables. In addition to ref-readonly parameters, in C# 7.2 users will be able to declare local variables as ref readonly.
Bugs this fixes:
Mentioned in #19216
Workarounds, if any
New feature!
Risk
Ref readonly locals follow in the footsteps of ref-readonly parameters. Almost everything dangerous with ref readonly locals can be done with ref readonly parameters, and the same checks are applied to ref readonly locals as to ref readonly parameters. The codegen for many of these cases has been manually verified to be safe.
Performance impact
Low. A few small checks on existing codepaths.
Is this a regression from a previous update?
No.
Root cause analysis:
New feature!
How was the bug found?
During test plan review it was decided that not supporting ref readonly locals was too painful to ship to users.
Test documentation updated?
The following scenarios have been manually tested for the IDE: