-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add speclet for ref readonly
parameters
#7165
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
Conversation
jjonescz
commented
May 4, 2023
- [Proposal]: ref readonly method parameters (VS 17.8, .NET 8) #6010
- Test Plan: ref readonly roslyn#68056
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.
(Previously had this as a comment, deleted that and put it as a review so it can be replied to easier, and marked as resolved when done - edit: I thought this would achieve something 😅)
How does this interact with function pointers?
It would be nice if we could have ref readonly
instead of in
on function pointers, not just actual methods.
e.g. delegate*<ref int, in int, out int, void>
currently gets emitted as method void *(int32&, int32& modreq([System.Runtime]System.Runtime.InteropServices.InAttribute), int32& modreq([System.Runtime]System.Runtime.InteropServices.OutAttribute))
.
Presumably we could emit a ref readonly int
input parameter as int32& modreq([System.Runtime]System.Runtime.InteropServices.InAttribute) modreq([System.Runtime]System.Runtime.CompilerServices.RequiresLocationAttribute)
(or a modopt).
If we allowed this, we'd probably want to allow &Method
which has ref readonly
parameters to be implicitly castable to the same with in
, and vice versa, to avoid source breaking changes.
Otherwise, we'd have to disallow ref readonly
for function pointer parameters, or not actually encode it in binary, neither of which are ideal imo.
Function pointers, method group conversions, method override and interface implementation generally fall into the same categories. The behavior in one generally follows to the other. My expectation is that whatever decisions comes from the override case will apply to the others. |
@jaredpar Function pointers already encode |
It's also present on
Where did I say they shouldn't be mentioned? My statement is that their behavior should follow how method group, overidde, etc ... work. That is fairly standard for this type of question. Once we establish that we'll codify how all of them work. |
I think @hamarb123 meant function pointers, which indeed do have |
I would like to address a concern that somehow the feature is "dead on arrival" if the existing APIs cannot simply use the new nice syntax without introducing a binary breaking change. I do recognize that there is a set of existing APIs that is relatively hard to consume today because C# doesn't allow passing readonly lvalues to them. The APIs have this shape because the authors would never want a reference to an rvalue to be passed as an argument, the On the other hand, since we are proposing to spend a nice language syntax, which is a very very scarce "resource", we should spend it very wisely. And I would say, we should spend it on the design that we would arrive to at the time we designed I think that the issue around consumption of the existing APIs can be addressed slightly differently. For example, the affected ref parameters could be decorated with an special attribute (quite possibly we will be able to reuse some existing attribute), which simply instructs the compiler to let readonly lvalues through because the method guarantees to not write through the reference. Support for this attribute can be implemented under umbrella of this feature as well. The issue with consumption of the APIs is addressed, there is no back-compatibility issue and we can use language syntax for a nice, clean language design with strong very useful guarantees to use for anyone on new APIs. |
This is largely inaccurate, at least for the BCL. The majority of impacted APIs (dotnet/runtime#85911 is a non comprehensive list, but covers most of the core cases) were exposed in the 15 years before Once The reason for these latter cases is because it is most typically a user bug to pass in an
They don't lose that guarantee, no more than they already have today. The codebase will emit a warning, and as with all warnings a user ignoring it is on them. A user already could do the wrong thing by explicitly spilling the value to the stack and calling APIs do gain something much more valuable in that they can:
This sounds like introducing a third feature that does the same general thing. I don't think we need an extra feature to cover the transition of |
My point is that the desire to transition the existing APIs without binary breaking change should not be the main driving factor for the design. Let's figure out how do we want the feature to work. Then we can evaluate if that helps addressing issues around existing APIs, and alternatives for cases that aren't addressed. And, BTW, a warning doesn't guarantee anything, in my opinion. |
I understand and the primary interested parties heavily weighed in on the initial design proposal (#6010) which has already gone to LDM and had many of these points raised/discussed. This comes across as rehashing the same points that were already debated fairly extensively.
Different users have different opinions. It isn't a strong guarantee but for most users they are more than sufficient and the more common scenario to be encountered. This is particularly the case for those that have Most things we surface to users about "you might be doing something wrong" are warnings and this is really no different. It's why we had the discussion around whether this could be an analyzer with the reason why not largely being that a language feature was needed anyways (to allow safely changing from |
Yes, that is the case. And my comments are not blocking for this PR. However, I think it is worth brining this to LDM as soon as possible to confirm the course. |
As a likely user of this feature, if it were made a breaking change from |
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.
Mostly lgtm, but I think we/LDM needs to discuss behaviour with function pointers more.
bb5b68d
to
a5c1f17
Compare
Specification will be extended to allow `ref readonly` modifiers for parameters with the same rules as specified for `in` parameters in [their proposal](https://github.com/dotnet/csharplang/blob/c8c1615fcad4ca016a97b7260ad497aad53ebc78/proposals/csharp-7.2/readonly-ref.md), except where explicitly changed in this proposal. | ||
|
||
Note that even though `ref` argument modifier is allowed for `ref readonly` parameters, nothing changes w.r.t. value kind checks, i.e., | ||
- `ref` can only be used with assignable values; |
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.
Does this mean I wouldn't be able to do the following? If so, it seems a bit weird:
int a = 1;
ref readonly int b = ref a; //note we use ref for this
ref readonly int c = ref b; //and for this
//would the following 2 lines be errors?
X1(ref c);
X2(ref c); //warning here obviously
void X1(ref readonly int x) { }
void X2(in int x) { }
I was hoping we would be able to just use ref
everywhere. It seems a bit odd to disallow ref
here, since we use ref
(not in
) for ref readonly
locals etc.
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, X1(ref c)
will be an error no matter whether X1
is defined with ref
/in
/ref readonly
parameter (the value kind of c
is checked against the ref
modifier at the callsite), you will have to use X1(in c)
when c
is readonly. The reason is that ref
at the call site says "passing a mutable reference" whereas in
says "passing a readonly reference" - ref readonly
parameters just allow both of them.
Ref assignments are special, they use ref
always, but it's pretty clear if it's readonly or not, since that's declared right there on the left-hand side.
242a7d6
to
5134bd7
Compare