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

Support for ref-readonly locals #22269

Merged
merged 4 commits into from
Sep 23, 2017
Merged

Conversation

agocke
Copy link
Member

@agocke agocke commented Sep 21, 2017

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:

Category Feature/Description C# Signoff
Enable/Disable Feature Flags To completely enables/disable new compiler features in the compiler & IDE ✅  
Typing General Typing- Type and paste new constructs- Nothing interferes with verbatim typing ✅  
  Completion- Typing new keyword/construct names- Dotting off of new constructs- Matching part of the identifier is highlighted (including word prefix matches) [Visual Studio 2015 Update 1]- Target type preselection [Visual Studio 2017]IntelliSense filtering [Visual Studio 2017] ✅  
  Formatting - Spacing in and around new constructs- Spacing options- Format Document commandTools > Options settings should be respected  :free:
  Automatic Brace Completion (C# only) - Auto-insert close brace- Shift+Enter commit of IntelliSense and any pending brace completion sessions (Known issue: #18065) 🆓
  Indentation - Typing Enter in an unfinished statement indents the next line  :white_check_mark:  
Navigating Go To Definition - F12 from callsites to definition- Ctrl+click [Visual Studio 2017 version 15.4] ✅  
  Go To Implementation - Ctrl+F12 to jump from virtual members to their implementations- Jump from inheritable types to their implementations  :free:
  Find All References - Lists references to a symbol in "Find Symbol Results" window- Shows results in hierarchy grouped by definition [Visual Studio 2015]- Results should be groupable/filterable/classified appropriately [Visual Studio 2017] - Find All References on literals [Visual Studio 2017 version 15.3]  :white_check_mark:   
Viewing Colorization - Keywords, literals, and identifiers colored appropriately in code- Colors should theme appropriately- The "Blue Theme (Additional Contrast)" should have sufficiently dark colors  :white_check_mark:   
  Error Squiggles - Squiggles appear as expected on reasonable spans ✅  

@agocke agocke added the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label Sep 21, 2017
@agocke agocke changed the title WIP Support for ref-readonly locals Sep 22, 2017
@agocke agocke added Area-Compilers and removed PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. labels Sep 22, 2017
@agocke agocke requested review from VSadov, OmarTawfik and a team September 22, 2017 19:46
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.
@jaredpar
Copy link
Member

appoved pending reviews

@agocke
Copy link
Member Author

agocke commented Sep 22, 2017

ping @VSadov @OmarTawfik for reviews

/// </summary>
private LocalDefinition EmitDupAddress(BoundDup dup, AddressKind addressKind)
{
if (!HasHome(dup, addressKind != AddressKind.ReadOnly))
Copy link
Member

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));
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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)

Copy link
Member

Choose a reason for hiding this comment

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

i am wrong


In reply to: 140600043 [](ancestors = 140600043,140599487)

@@ -268,7 +268,7 @@ internal virtual bool IsWritable
case LocalDeclarationKind.UsingVariable:
return false;
default:
return true;
return RefKind != RefKind.RefReadOnly;
Copy link
Member

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?

Copy link
Member Author

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++;

Copy link
Member

Choose a reason for hiding this comment

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

Same thing that happens if xr is a readonly static


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

// (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)
);
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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)

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.

LGTM
with some test suggestions

@VSadov
Copy link
Member

VSadov commented Sep 22, 2017

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;
Copy link
Contributor

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)
Copy link
Contributor

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;
Copy link
Contributor

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?

Copy link
Member Author

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()
Copy link
Contributor

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(
Copy link
Contributor

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.

Copy link
Member Author

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();
Copy link
Contributor

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?

Copy link
Member Author

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
Copy link
Contributor

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.

Copy link
Member Author

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);
Copy link
Contributor

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?

Copy link
Member Author

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.

@OmarTawfik
Copy link
Contributor

Should we add readonly to the suggestion list in the IDE?

Copy link
Contributor

@OmarTawfik OmarTawfik left a 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.

@agocke
Copy link
Member Author

agocke commented Sep 23, 2017

@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.

@OmarTawfik
Copy link
Contributor

@agocke I suggest then a test in ReadOnlyKeywordRecommenderTests.cs

@agocke
Copy link
Member Author

agocke commented Sep 23, 2017

@OmarTawfik Sounds good, I'll follow up with a bunch of IDE regression tests in a later PR.

@agocke agocke merged commit fef4159 into dotnet:master Sep 23, 2017
@agocke agocke deleted the readonly-ref-locals branch September 23, 2017 18:11
333fred added a commit to 333fred/roslyn that referenced this pull request Sep 25, 2017
* 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
  ...
333fred added a commit to 333fred/roslyn that referenced this pull request Sep 28, 2017
* 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
  ...
@benaadams
Copy link
Member

Yey! Was just about to raise issue for this - wasn't sure how you were meant to use a ref readonly return - other than directly and once; or by using the function call output as the parameter to another function that takes an in

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.

None yet

7 participants