-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Naming style for local functions #26165
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
| SyntaxNodeAnalysisContext context, | ||
| ConcurrentDictionary<Guid, ConcurrentDictionary<string, string>> idToCachedResult) | ||
| { | ||
| var symbolContext = new SymbolAnalysisContext( |
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 pretty surprised this even had a public constructor you could use in this manner. @mavasani expected?
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.
me too
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.
resolved in 3a806e7
| OnCompilationStartAction(context, idToCachedResult); | ||
| } | ||
|
|
||
| protected virtual void OnCompilationStartAction( |
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.
can you make abstract, so all subclasses have to think about what the right thing to do 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.
I usually prefer abstract methods but this is sort of like an event... you shouldn't think about it unless you need it. The local function thing is really a special case.
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.
hrmm.. i'm on the fence.
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.
fixed in e2356ba
| internal class NamingStylePreferences : IEquatable<NamingStylePreferences> | ||
| { | ||
| private const int s_serializationVersion = 4; | ||
| private const int s_serializationVersion = 5; |
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.
what impact does thsi have on people upgrading? will their existing preferences be honored?
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 don't think so :/ this should reset your preferences
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'll take a look at what I can do to migrate them, but this is really hard to test
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.
If we don't increase the version, the impact on newer versions would be that your settings for methods now apply to local functions as well.
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.
ok. but you've confirmed that if you do update the version then previous settings are preserved and not wiped out?
Can we just write code that migrates across versions, and converts an existing method rule so that it only applies to ordinary methods, and not local functions? (we def need tests here as well).
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.
the code in FromXElement a few lines below does wipe them 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.
the code in FromXElement a few lines below does wipe them out
Ok. we probably need to fix that. and have this code actually upgrade things from v4 to v5.
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.
note: the upgrading codpath should run all the time, and it shoudl upgrade whenever the 'prior' value is before the change point.
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 can't find any code in this PR related to ensuring user preferences are not wiped out. can you point me to it?
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.
fixed in 8feada7
| : new XElement(nameof(TypeKind), TypeKind); | ||
| => SymbolKind.HasValue ? new XElement(nameof(SymbolKind), SymbolKind) : | ||
| TypeKind.HasValue ? new XElement(nameof(TypeKind), TypeKind) : | ||
| MethodKind.HasValue ? new XElement(nameof(MethodKind), MethodKind) : |
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.
what's the impact in the other direction. will previous versions of VS properly ignore this and not crash if you use this and have preferences-roaming on?
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.
they should ignore it, the code in GetSymbolKindListFromXElement looks for names it knows, but I can't confirm... I think we need to upgrade the options
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.
previous VS versions will notice that the version is different than what they like and they will reset the options
|
@Neme12 Thanks! I'll look at the PR this week. A few questions:
|
| await VerifyItemExistsAsync(markup, "MyClass", glyph: (int)Glyph.MethodPublic, expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name); | ||
| await VerifyItemExistsAsync(markup, "myClass", glyph: (int)Glyph.FieldPublic, expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name); | ||
| await VerifyItemExistsAsync(markup, "MyClass", glyph: (int)Glyph.PropertyPublic, expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name); | ||
| await VerifyItemExistsAsync(markup, "GetMyClass", glyph: (int)Glyph.MethodPublic, expectedDescriptionOrNull: CSharpFeaturesResources.Suggested_name); |
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 have absolutely no idea why this changed... I'll try to investigate but for now I'm changing the test because the new behavior is better than the old one. MyClass now has a property rather than method glyph, which seems more appropriate. GetMyClass still has a method glyph as expected.
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.
ok with this change?
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.
ok turns out this revealed an actual bug... your custom naming styles for methods now don't show up here... this is because a setting for MethodKind.Ordinary now doesn't match for a general SymbolKind.Method. This feature only tries to determine the possible SymbolKind of the declaration I'm typing, one of which is Method, but a code style setting for MethodKind.Ordinary doesn't match against any kind of Method. I have to fix this :/ and also add tests... The logic for determining which declaration I'm typing needs to be updated to recognize both methods & local functions and not lump them together in one SymbolKind.Method.
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.
The fix involves fixing the name suggestion feature to respect settings for each individual method kind... for example if you have a certain suffix for methods but a different one for local functions, it should respect your style for each one depending on what you're naming and offer suggestions based on that.... this got a lot more complicated than I thought
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.
Fixed in 8e556d2. I modified DeclarationNameCompletionProvider to differentiate between locals, local functions & methods. I did it in such a way that this icon change above is preserved by reordering the symbol checks to match properties first. I'll add more tests later.
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 sorry but the test helpers look like a giant mess, many of the methods have 10 parameters, some of them are even virtual, and they use flags and null to determine what it's going to actually check in the first place. I don't know how to add anything to that,
You don't have to add parameters to existing methods. You can just make a new method. Something that asserts the order of the completion list.
and don't really feel like touching them at all.
Testing is part of coding. Just because an existing test system isn't in great shape doesn't mean that testing functionality can be skipped :)
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.
the test is added as i said above
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.
also this should have been tested before by the person who added the logic to customize the order
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.
Sometimes we're not always lucky enough that the person before did all the things we wished :)
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.
Great!
sharwell
left a comment
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'd like to take a closer look at this. Marking request changes only do it doesn't get accidentally merged before I can check it out on Wednesday.
The sceenshots only show the list and definition of symbol specifications, which is only one part of naming style settings. The whole design consists of:
Note that I didn't change any of that. I didn't even change the default set of specifications to include one for local functions. I believe some people might want to include them together with their existing specifications for methods rather than adding a separate one. The only thing I added is the ability to choose "local function" as part of a symbol specification. You can create any kind of naming rule with this.
Yes, but the list for symbols also contains parameters, which can't have any accesibility either. In fact the "modifiers" list will show irrelevant modifiers most of the time depending on what symbols you choose. We currently don't doo any filtering to narrow down the list of modifiers/accesibilities based on what set of symbols you have selected. I would prefer if I don't have to fix that. Also note that they can have one of the modifiers - async. One other modifier I can think of is unsafe but that one isn't included as one of the options.
There are three rules by default.
There is a ton of default specifications however that you can see in the first screenshot. |
|
@Neme12 The PR is very heavy due to the rename of the SymbolAndTypeAndMethodKind. Can we undo that rename for now, and just get the primary changes in. Then, once it has passed review, the name change can be made. Thanks! |
| isSupportedDiagnostic: _ => true, | ||
| context.CancellationToken); | ||
|
|
||
| SymbolAction(symbolContext, idToCachedResult); |
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.
instead of this approach, i woudl prefer if we registered a Symbol-op and a Syntax-op, and they just called through to the same final helper method, rather than synthesizing up a SymbolAnalysisContext instead and having the Syntax-op call through the Symbol-op version.
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 don't know how. SymbolAction uses ReportDiagnostic from the context, Options and there is also an extension method on the context that it uses.
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.
fixed in 3a806e7
| private static readonly SymbolOrTypeOrMethodKind _event = new SymbolOrTypeOrMethodKind(SymbolKind.Event); | ||
| private static readonly SymbolOrTypeOrMethodKind _delegate = new SymbolOrTypeOrMethodKind(TypeKind.Delegate); | ||
| private static readonly SymbolOrTypeOrMethodKind _parameter = new SymbolOrTypeOrMethodKind(SymbolKind.Parameter); | ||
| private static readonly ImmutableArray<SymbolOrTypeOrMethodKind> _all = |
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.
these bits of code are very hard to review since we have to go and validate each and every line. If you undo the rename it will make it much easier. Then the rename can happen at teh end once the overall logic has been validated.
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.
fixed in 56df385
| new SymbolOrTypeOrMethodKind(MethodKind.LocalFunction), | ||
| new SymbolOrTypeOrMethodKind(SymbolKind.Field), | ||
| new SymbolOrTypeOrMethodKind(SymbolKind.Event), | ||
| new SymbolOrTypeOrMethodKind(SymbolKind.Parameter)), |
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.
these lists are a PITA to review.
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.
fixed in 56df385
I can rename it back but unfortunately I don't think it's going to be easier to review because I updated almost every single usage of it. |
There looked to be lots of cases where it was just a rename. so if we could change it back, it would be helpful. Thanks! |
6362d1d to
56df385
Compare
|
Question: @kuhlenh was asking for support for local variables... should I add them in as well? I think adding them on top of this would be easy with no extra changes required. EDIT: well, there are many different places where you can declare a local in the language (and it's different for VB) so maybe it would be good for a separate PR after all... adding locals shouldn't require changing the serialization version so it's fine to do it separately. |
|
@Neme12 Thanks for the answer. Makes sense. |
|
@jcouv Yes! Here's an example: If we later add support for locals as well, you could include them both in 1 specification if you want: |
| foreach (var kind in declarationInfo.PossibleSymbolKinds) | ||
| { | ||
| var kind = new SymbolKindOrTypeKind(symbolKind); | ||
| // There's no special glyph for local functions. |
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 probably should be. The Glyph enum has everything - ClassPublic, StructPublic, FieldPublic, FieldPrivate, Local, Parameter... local functions are clearly missing. They're just treated as private methods.
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.
Can you open a bug?
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 #26327
|
Pinging @dotnet/roslyn-ide specifically. This should get some more eyes on it. |
| { | ||
| workspace.Options = workspace.Options.WithChangedOption( | ||
| new OptionKey(SimplificationOptions.NamingPreferences, LanguageNames.CSharp), | ||
| NamesEndWithSuffixPreferences()); |
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.
do these tests run in parallel? if so, this would be a problem. Or is parallel running opt-in?
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.
this is what the existing tests do
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 thought there was a new test class instance created for each test, isn't that the case?
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.
and yes, parallel running is disabled by default
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.
Great. Thanks!
| // HACK: RegisterSymbolAction doesn't work with local functions | ||
| context.RegisterSyntaxNodeAction(SyntaxNodeAction, SyntaxKind.LocalFunctionStatement); | ||
|
|
||
| void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext) |
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.
can you have an explicit "return" before this local function, then add a ``// local functions``` comment. it makes it much easier to tell that htese are locals, and not methods.
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.
fixed in e2356ba
| context.RegisterSymbolAction(c => SymbolAction(c, idToCachedResult), _symbolKinds); | ||
| context.RegisterSymbolAction(SymbolAction, _symbolKinds); | ||
|
|
||
| void SymbolAction(SymbolAnalysisContext symbolContext) |
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.
please place local functions after all normal method control flow. have an explicit 'return' before it, and add ``// local functions``` comment. thanks!
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.
"// local functions" comment? :-/ that seems to me like "warning, new language features!"
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. Because local functions require extra attention over a normal method. Especially related to understanding capture flow. There is no obvious visual distinction between them and methods, so having the comment is very helpful. Otherwise, especially in github, you have to scroll all the way up to figure out if you are preceded by a statement and thus are a local function vs a sibling method.
Similarly, groupign all the local functions together, and not interspersing them in code, makes things much easier to analyze as you can look at the actual logic and code-flow in a single pass, without having random declarations interspersed that you have to jump past.
Like with all language constructs, there are ways to use them that can aide readability and comprehension and ways to use them that can hinder them. This is just like informal rules the IDE has about complexity in expressions and other things. You are not disallowed from using these features, but please use them in manners that are most conducive to a team environment.
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.
fixed in e2356ba
| symbolContext.CancellationToken); | ||
|
|
||
| if (diagnostic != null) | ||
| symbolContext.ReportDiagnostic(diagnostic); |
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.
curlies.
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.
fixed in e2356ba
| syntaxContext.CancellationToken); | ||
|
|
||
| if (diagnostic != null) | ||
| syntaxContext.ReportDiagnostic(diagnostic); |
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.
curlies
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.
fixed in e2356ba
|
@sharwell What would be the best design to achieve that? I got a few ideas:
storageLocations: new OptionStorageLocation[]{
new NamingStylePreferenceEditorConfigStorageLocation(),
new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.NamingPreferences2"),
new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.NamingPreferences")});
|
|
@Neme12 Did not think of option 1 at all, but if it works and isn't an overwhelming about of code I love it! |
| GetNodeDenotingTheTypeOfTupleArgument, | ||
| _ => default(SyntaxTokenList), | ||
| _ => ImmutableArray.Create(SymbolKind.Local), cancellationToken); | ||
| _ => ImmutableArray.Create(new SymbolKindOrTypeKind(SymbolKind.Local)), cancellationToken); |
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.
This is technically wrong. User's preferences for locals shouldn't apply to tuple name suggestions. This needs to be fixed as soon as naming preferences for tuples are implemented.
|
@sharwell I'm trying to manually test the behavior with roaming settings and the parallel settings for different versions work correctly, but I don't know how to test the upgrade path since that would require resetting roaming settings that are associated with my account. Do you know if there's a way to do that? |
I'm not sure. @olegtk are you aware of a test strategy for this? |
…format into an older version
| { | ||
| var storageKey = roamingSerialization.GetKeyNameForLanguage(optionKey.Language); | ||
|
|
||
| RecordObservedValueToWatchForChanges(optionKey, storageKey); |
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 not completely certain whether this is the right thing to do. Should only ever listen to changes in the newest location? Or in the one that we found?
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.
This seems the most correct - any change to the location we found or any previous ones will affect the result. I'm just not sure whether this will work properly.
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.
Actually, I don't see any problem with this after looking at the implementation. But I don't know how to verify the 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.
I see why this seemed confusing, but I agree with the conclusion.
| storageLocations: new OptionStorageLocation[] { | ||
| new NamingStylePreferenceEditorConfigStorageLocation(), | ||
| new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.NamingPreferences")}); | ||
| new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.NamingPreferences[5]"), |
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 used 5 instead of 2 after NamingStylePreferences.s_serializationVersion, which is 5.
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.
❔ Are we sure brackets are allowed? I was expecting to see NamingPreferences5.
|
@sharwell I was able to verify this manually by changing the location name (so that it doesn't exist in my roaming settings). But I'm still a little worried about the fact that there are no unit tests for |
| storageLocations: new OptionStorageLocation[] { | ||
| new NamingStylePreferenceEditorConfigStorageLocation(), | ||
| new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.NamingPreferences")}); | ||
| new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.NamingPreferences[5]"), |
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.
❔ Are we sure brackets are allowed? I was expecting to see NamingPreferences5.
I verified this after the change to brackets and it worked correctly. I thought it might be nice to use a special convention for different versions of the same value, to clearly differentiate this pattern. But I don't feel strongly about this, please let me know you would prefer to change it back. |
|
@sharwell I made a minor change to fix an outdated comment I noticed while reviewing the code and added a proper test case now that naming for local variables has been merged. |
|
retest this please |
|
is CI stuck? |
|
@Neme12 Not sure. Will get back to you on the naming later today. We'll deal with the CI when we're trying to get it merged. 😄 |
@sharwell any new info on that? Also, will this PR target 15.8 or not (since the changes in VS persistence might be risky)? |
|
@Neme12 We reached a weak consensus on I am planning this for 15.8 along with the change to the meaning of empty/ |
|
@jinujoseph for approval |
|
Approved to merge for 15.8.Preview3 |
|
retest windows_debug_spanish_unit32_prtest please |




fixes #22440
I added a new option for local functions because it seems that people are divided on whether they should be named as locals or methods. For the same reason, I think we shouldn't give them a default style for now.
They're not even included in the default set of specifications. Users can to either include them with methods or add a new specification.