-
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
Add ReturnOnly scope #64090
Add ReturnOnly scope #64090
Conversation
e6b9c01
to
7a23527
Compare
7a23527
to
ec524e6
Compare
@@ -794,21 +797,17 @@ private uint GetParameterValEscape(ParameterSymbol parameter) | |||
|
|||
private uint GetParameterRefEscape(ParameterSymbol parameter) | |||
{ | |||
if (UseUpdatedEscapeRules) |
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 suspect that more places where this check is used can be simplified. If the ParameterSymbol APIs behave appropriately depending on which escape rules are in use, then doing these checks separately could be redundant.
Diagnostic(ErrorCode.ERR_RefFieldCannotReferToRefStruct, "ref R1<T>").WithLocation(8, 12), | ||
// (15,9): error CS9079: Cannot ref-assign 'r1' to 'F' because 'r1' can only escape the current method through a return statement. | ||
// r2.F = ref r1; | ||
Diagnostic(ErrorCode.ERR_RefAssignReturnOnly, "r2.F = ref r1").WithArguments("F", "r1").WithLocation(15, 9)); |
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.
Had some ideas for tests we need to validate this change. I'm sure we have a lot of these already but was having trouble finding them all. Decided to just write out the core scenarios that I thought were impacted by this change. More than happy if these are already covered.
https://gist.github.com/jaredpar/880869bcb54e38cb1e1475b1ff2543ef
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.
Looked around a bit and didn't feel like existing tests were quite similar enough. Added these tests in e582e59. I think some of the tests assumed that RTRS would have ReturnOnly scope by default (added [UnscopedRef]
to compensate). Also, ReturnOnlyScope_03
has some cases where we assign a ref to a non-ref field, which I'm not sure was intentional.
return parameter.RefKind == RefKind.None ? Binder.TopLevelScope : Binder.ExternalScope; | ||
} | ||
{ RefKind: RefKind.None } or { EffectiveScope: not DeclarationScope.Unscoped } => Binder.TopLevelScope, | ||
{ EffectiveScope: DeclarationScope.Unscoped, Type.IsRefLikeType: true } => Binder.ReturnOnlyScope, |
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.
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.
Yes. The state machine won't test the property again in this path, though.
It feels like it might be good to either:
- add
RefKind: not RefKind.None
to make the case fully self-describing, or - remove
EffectiveScope
to try and reduce redundancy.
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 went with removing.
static R F4([UnscopedRef] ref R y) { return F0(__arglist(ref y)); } // 5 | ||
}"; | ||
// __refvalue operators can only ref-escape to current method. | ||
// __arglist operators essentially assume that anything can escape into anything else, including a ref into its referenced variable. |
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.
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.
filed #64130.
// (17,24): error CS8170: Struct members cannot return 'this' or other instance members by reference | ||
// S Prop4 => new S { refField = ref this.field }; // 2 | ||
Diagnostic(ErrorCode.ERR_RefReturnStructThis, "refField = ref this.field").WithLocation(17, 24)); | ||
} |
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 adding corresponding tests for instance methods that return ref structs with ref fields:
S F4() => new S { refField = ref this.field }; // 2
[UnscopedRef] S F5() => new S { refField = ref this.field }; // okay
And consider adding tests for static methods:
static S F4(S r) => new S { refField = ref r.field }; // 2
static S F5([UnscopedRef] S r) => new S { refField = ref r.field }; // okay
And tests that use an intermediate local:
static S F4(S r) { S s = new S { refField = ref r.field }; return s; } // 2
static S F5([UnscopedRef] S r) { S s = new S { refField = ref r.field }; return s; } // okay
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 you meant to have ref S r
, in the static method case since that's needed to be able to return ref r.field
.
I'm going to skip the intermediate local case, I feel we've reliably covered that ref locals get their RSTE from their initializer.
static bool condition() => false; | ||
|
||
static void M1(ref S p1, ref S p2) { | ||
p2.field = ref p1.field; // 1 |
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.
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.
yes this should be p2.refField = ref p1.field
. Think I had that wrong in the gist.
p2.refField = ref p1.refField; // Okay | ||
} | ||
|
||
// The [UnscopedRef] moves `out` to default RSTS which is *return only* |
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.
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'm now thinking about how many times I've written RSTS when I meant RSTE ... a lot
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.
we have spent a lot of time talking about RSTE of RTRS, so..
|
||
static S2 Inner1([UnscopedRef] ref S s) => new S2 { S = s }; | ||
static S2 Inner2(scoped ref S s) => new S2 { S = s }; | ||
} |
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 adding:
static S2 M9([UnscopedRef] ref S p)
{
if (condition()) return Inner1(ref p);
return Inner2(ref p); // 9
static S2 Inner1([UnscopedRef] ref S s) => new S2 { S = s };
static S2 Inner2(ref S s) => new S2 { S = s };
}
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.
Did you expect return Inner2(ref p)
to be an error here? Why? It looks like ref S p
has ReturnOnly
scope, so it's OK to return ref S
through Inner2
(although that doesn't actually happen, that's what the signature of Inner2
indicates)
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.
Did you expect return Inner2(ref p) to be an error here?
Inner2
essentially has the signature
static S2 Inner2(scoped ref S s) => ...
Given current state of the code base. The call to return Inner2(ref p)
should be safe no matter the RSTE of ref p
as it can't be returned
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.
my brain is constantly switching the RSTE of RTRS between ReturnOnly and CurrentMethod. we need to get #64122 done so the pain will stop
static bool condition() => false; | ||
|
||
static void M1(ref S p1, ref S p2) { | ||
p2.field = ref p1.field; // 1 |
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.
yes this should be p2.refField = ref p1.field
. Think I had that wrong in the gist.
p2.refField = ref p1.refField; // Okay | ||
} | ||
|
||
// The [UnscopedRef] moves `out` to default RSTS which is *return only* |
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'm now thinking about how many times I've written RSTS when I meant RSTE ... a lot
Closes #64100