-
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
Dont suggest this in static local functions #35822
Dont suggest this in static local functions #35822
Conversation
tagging @dotnet/roslyn-compiler @jcouv . Note: this PR should be reviewed commit by commit as it shows first the safe mechanical translation. |
I'm going to copy some of the internal discussion we had around this last time I made a PR in this space (lightly pruned for irrelevant things). We did not come to any conclusions about this at that point, and my PR languished until I closed it as it didn't seem to matter. This provides a reasonable use case, and I'm in favor of solution 1. @agocke said:
@DustinCampbell said:
@gafter said:
@333fred said:
@agocke said:
@333fred said:
@agocke said:
@agocke said:
@gafter said:
|
Note: i agree with this:
It seems like it would be very bizarre to have the language concept of "local functions" and "static local functions" and then not have IsStatic just be the natural way to distinguish those. |
@YairHalberstadt: what did "make ISymbol.IsStatic and don't add a new member" ultimately look like? Did IDE tests break or did things still work? |
I'm not sure what you're referring to by this? |
@YairHalberstadt Oh nevermind that's effectively this PR. I saw this adding stuff to Symbol and read that as ISymbol. Nevermind. Time to find some caffine. |
/// <summary> | ||
/// Returns true if this symbol requires an instance reference as the implicit reciever. This is false if the symbol is static, or a <see cref="LocalFunctionSymbol"/> | ||
/// </summary> | ||
public virtual bool RequiresInstanceReciever => !IsStatic; |
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.
Reciever [](start = 44, length = 8)
Typo: Receiver
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 always get my "ei"s and "ie"s mixed up :-)
Anyway, fixed now.
I've fixed the broken test. The issue was a place where I thought on an initial look that it wouldn't be necessary to convert I think this just highlights - I will have to go through all usages of On the other hand, a large percentage can be ruled out as irrelevant at first sight. I'd estimate about 50 - 100 references have to be checked properly. |
/// <summary> | ||
/// Returns true if this symbol requires an instance reference as the implicit reciever. This is false if the symbol is static, or a <see cref="LocalFunctionSymbol"/> | ||
/// </summary> | ||
public virtual bool RequiresInstanceReceiver => !IsStatic; |
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.
It feels like this is probably the wrong implementation approach. I do think that the public ISymbol.IsStatic
should reflect what this PR achieves, but there's a lot of internal code that depends on the implementation of IsStatic
. A less risky approach would be to explicitly implement the public version of IsStatic
separately from the internal Symbol.IsStatic
, and doc what the difference is.
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 the discussion you quoted, I think @agocke argued that:
I can follow along this logic, but the PR looks like ISymbol.IsStatic will return the old behavior (always true). How are we rationalizing the difference between the public and private API? Why should our private API return something different from our public API?
If the argument is that this is a breaking change because it’s the right thing to do, shouldn’t we “put our money where our mouth is” and change the behavior in the internal compiler implementation as well?
It doesn't look like that was ever resolved.
I'm happy to go either way once a decision is madr
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.
@agocke is oof for the next couple of weeks, so @gafter would you care to weigh in? I think the real question here is whether we should be pragmatic and attempt to change the compiler implementation as little as possible, or whether we should attempt to adapt our use cases and potentially miss some?
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 must admit I'd be a little worried about:
A less risky approach would be to explicitly implement the public version of IsStatic separately from the internal Symbol.IsStatic, and doc what the difference is.
It does reduce risk now, but I'd then be worried if our public API was getting the same attention of the internal code. Or also if the compiler at one point would accidentally call the ISymbol implementation and get the "wrong" behavior.
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.
@333fred @agocke @gafter Which approach do we want to take here? I think @YairHalberstadt is blocked on it at this point and we can address the lack of tests or other concerns once we sort that out.
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 haven't reviewed the PR in full yet, but I like the approach @YairHalberstadt is taking here.
My primary concern with the whole thing is that we have a very loose contract on what "IsStatic" actually means, but then we used IsStatic in a lot of the code to make very specific language determinations.
From my perspective, the right approach is to pull out a new API, like @YairHalberstadt has done with RequiresInstanceReceiver, give that a strictly defined meaning, and then use that to make the appropriate language decisions.
Given this path I would actually argue that in a perfect world we would remove ISymbol.IsStatic
since I can not think of a description in a doc comment that would be useful to a user, but clearly that ship has already sailed.
I don't see any test changes here? I would expect to see some compiler tests added to cover the the return values of the public API. Apparently we might have a test gap here. Done review pass (commit 4) |
@YairHalberstadt I do see you added additional tests and if @agocke is liking the approach, then is this on us to get this reviewed again? |
@YairHalberstadt Anything you need from us? |
Sorry - I didn't see you were asking me something in the previous message. No - this is good to review. Thanks |
@dotnet/roslyn-compiler: can we get a few reviews of this community PR then? Please note the conversation above regarding the design decision there. I think I'm willing to say the compiler goes first here and once signed off the IDE will review and then merge. This is a case where the IDE really doesn't have any big skin in the game here -- it's all about the API you all get to support and maintain. |
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.
Sorry, one thing -- I agree that having RequiresInstanceReceiver on Symbol itself is pretty shaky. Could we put it on the deriving symbols instead and do a type check at the appropriate points?
It feels like we're reproducing the earlier mistake of making the API so broad it doesn't mean anything
By my reckoning that means:
Please chime in if I missed one :) |
Requested changes made |
src/Workspaces/CSharp/Portable/Extensions/ContextQuery/SyntaxTreeExtensions.cs
Show resolved
Hide resolved
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
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
@jasonmalinowski I'll leave it to you to sign off on the IDE side |
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.
IDE change is fine other than yes, we want braces even on trivial ifs. ;-)
The one thing I don't quite get though -- so the compiler now has a new API internally, but we're not making that public and instead continuing to make IsStatic public? It seems odd if we're saying "we can't rationalize what IsStatic means...so we'll just change it to whatever it needs to be"?
Also @agocke and @RikkiGibson is this something you want in 16.2 or 16.3? The IDE changes are obviously trivial and if this was an IDE-only fix we'd take in 16.2. Since it's churning your stuff there's extra risk so I'm happy to defer to you. |
@jasonmalinowski Well, I think the new API can be internal for now, and public if it becomes necessary for other people, same as our standard API process. As far as target, let's wait until the snap tomorrow to merge, since it's < 24 hours away |
Add braces around trivial if in SyntaxExtensions
Alright, I will merge this later today post snap, which pushes this into shipping in 16.3. |
Thanks @YairHalberstadt for the awesome contribution, especially for one that always has the fun mix of design issues and cross compiler/IDE work! |
* updated to Roslyn 3.3.0-beta1-19365-07 * support sync namespace refactoring * do not run into null reference * handle newly created files * better relative path handling * small fixes * improve tests * added SyncNamespaceFacts * fixed test and added more resiliency * added support for live changing of root namespace * added more tests - for live reload of a namespace * code review feedback * roslyn 3.3.0-beta2-19376-02 * reflection fix due to a method change from public to internal in SymbolKey * fixed code action tests * address changes to dotnet/roslyn#35822 * fixed cake test
…ways return true for all local functions, but now with C# 8 static local functions feature, it only returns true for local functions declared with a static modifier. See dotnet/roslyn#35822 for details.
Could you please check #39565? The regression seems to be related to these changes. An exception is incorrectly thrown thinking that it requires an instance receiver. Thanks for your help. |
See #35644, #27719
The Problem
Currently
Symbol.IsStatic
is not very well defined. The compiler assumes it means any/all of the following:this
In general this has worked till now because these definitions have mostly coincided, and when they haven't it has usually been in an understandable way (eg. constants are static even though they are not marked static).
Unfortunately Local Functions break that:
Static local functions are marked static, cannot capture this, and do not require an instance receiver.
Non-Static local functions are not marked static, can capture this, but do not require an instance receiver.
Currently all local functions are marked static, in order to make the compiler work with the fact that they do not require an instance receiver. This causes two problems:
It is non-intuitive that a non-static local functions IsStatic returns true. Since this is a public API, that's a problem.
We need an API to tell us if a local function is static or not. Indeed the ultimate purpose of this PR is to prevent
this
being suggested in local functions, which depends on such an API.Solutions
We need to decide what IsStatic means:
Option 1.
Make non-static local functions IsStatic return false. Replace all usages of IsStatic where we are checking to see if a member Requires an instance receiver with a new property, RequiresInstanceReciever. This property can be internal, since it is only really of interest to the compiler.
advantages
IsStatic now matches our intuition much better.
We've now increased the explicitness of the compiler. Rather than mashing up lots of different concepts into one, we've begun seperating them out into different concepts.
disadvantages
This is risky. RequiresInstanceReciever now behaves exactly like IsStatic used to, but IsStatic behaves slightly differently. If we forget to replace all relevant usages of IsStatic with RequiresInstanceReciever, we may introduce subtle bugs that only occur with non-static local functions.
Option 2.
We currently have a temporary internal API
IsStaticLocalfunction
. Make this public and move it to IMethodSymbol. Document what IsStatic means.advantages
Very low risk. Simple to do.
disadvantages
IsStatic now doesn't match our intuitions.
Rather than solving the problem, we've worked around it. We've just increased the technical debt of the codebase, rather than decreasing it.
Approach taken here
The ideal solution is obviously the first, which is what I've done, so that the risk can be properly evaluated. I've created a pr for the second solution at #35825 so they can be compared.
Changing a call from
IsStatic
toRequiresInstanceReciever
is always safe, since the latter behaves like the former used to.Leaving a call as a call to
IsStatic
is dangerous, asIsStatic
now has different behaviour.For fields/events/properties
IsStatic
andRequiresInstanceReciever
are synonyms.I've gone through every usage of
IsStatic
in the compiler code (not tests/workspaces/IDE) and if it appears to be about receivers, and is not explicitly talking about a field property or event, I have replaced it with a call toRequiresInstanceReciever
.This requires changing 27 files.
Depending on the opinion of the roslyn team, I am happy to either take the alternative solution, do more work here, or drop this altogether.