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

Add ReturnOnly scope #64090

Merged
merged 9 commits into from
Sep 20, 2022
Merged

Add ReturnOnly scope #64090

merged 9 commits into from
Sep 20, 2022

Conversation

RikkiGibson
Copy link
Contributor

@RikkiGibson RikkiGibson commented Sep 16, 2022

Closes #64100

@@ -794,21 +797,17 @@ private uint GetParameterValEscape(ParameterSymbol parameter)

private uint GetParameterRefEscape(ParameterSymbol parameter)
{
if (UseUpdatedEscapeRules)
Copy link
Contributor Author

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.

@RikkiGibson RikkiGibson marked this pull request as ready for review September 16, 2022 22:34
@RikkiGibson RikkiGibson requested a review from a team as a code owner September 16, 2022 22:34
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));
Copy link
Member

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

Copy link
Contributor Author

@RikkiGibson RikkiGibson Sep 19, 2022

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,
Copy link
Member

Choose a reason for hiding this comment

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

EffectiveScope: DeclarationScope.Unscoped

Is this check unnecessary given the or above?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.
Copy link
Member

Choose a reason for hiding this comment

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

including a ref into its referenced variable

It looks the consumer cannot assign into the referenced variable though, at least according to // 1.

Copy link
Contributor Author

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

@cston cston Sep 19, 2022

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

Copy link
Contributor Author

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

@cston cston Sep 19, 2022

Choose a reason for hiding this comment

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

ref p1.field; // 1

Is the assignment of a ref to a value intentional in these cases?

Should these cases be p2.refField = ref p1.field; instead?

Copy link
Member

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*
Copy link
Member

Choose a reason for hiding this comment

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

RSTS

"RSTE"?

Copy link
Member

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

Copy link
Contributor Author

@RikkiGibson RikkiGibson Sep 19, 2022

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

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

Copy link
Contributor Author

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)

Copy link
Member

@jaredpar jaredpar Sep 19, 2022

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

Copy link
Contributor Author

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

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*
Copy link
Member

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

@RikkiGibson RikkiGibson merged commit fbe8ad2 into dotnet:main Sep 20, 2022
@RikkiGibson RikkiGibson deleted the return-only-scope-2 branch September 20, 2022 00:41
@ghost ghost added this to the Next milestone Sep 20, 2022
@Cosifne Cosifne modified the milestones: Next, 17.4 P3 Sep 26, 2022
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.

Implement the separate return scope
5 participants