-
Notifications
You must be signed in to change notification settings - Fork 4k
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
[#23030] Disable literal Ctrl+Click navigation #24910
[#23030] Disable literal Ctrl+Click navigation #24910
Conversation
|
||
return includeLiterals | ||
? (symbol ?? semanticInfo.Type) | ||
: symbol; |
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 see any check that is seeing we're on a literal or not.
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.
Literals don't have an AliasSymbol
, ReferencedSymbols
, or DeclaredSymbol
. Their type for navigation is determined through Type
.
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 assumption was: in case there is no [alias
, referenced
or declared
] symbol it is a literal
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 means no navigation on anything that doesn't have a symbol, but does have a type. I would prefer we actually just exclude literals (instread of trying back-infer that we were on a literal).
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 would recommend instead doing:
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var isLiteral = syntaxFacts.IsLiteral(whateverTokenYoureOn);
That would be an accurate check. 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.
You can do this in the code that creates the TokenSemanticInfo. And the TokenSemanticInfo can include that as a boolean in it. Or, alternatively, the TokenSemanticInfo can just contain hte token, and you can check it at the call site. Either way is fine with me.
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.
Perfect, thanks for the comments 👍
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.
After some investigation, I have some doubts. Token "literality": is it a semantic or syntactic information? In case you agree that is syntactic info, what do you think about adding struct TokenSyntacticInfo
and providing methods to work with it? Then I can use it in this PR and we can easily extend it with more properties if future tasks require them
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.
Token "literality" is it a semantic or syntactic information?
Currently, it's somethign that can be determined entirely syntactically.
In case you agree that is syntactic info, what do you think about adding struct TokenSyntacticInfo
I think that's overkill. TokenSemanticInfo is really just a struct that means "collect a bunch of useful information about this token for me." It doesn't mean "it can't contain anything syntactic in it".
Then I can use it in this PR and we can easily extend it with more properties if future tasks require them
Too speculative :) Right now we really don't need that. All we need to know is what token we're ok. Then ISyntaxFactsService can be trivially used to ask "Is this a literal".
Tagging @rchande You're putting tests in the normal f12-goto-def test path. @rchande Do you have specific tests anywhere that validate the behavior of ctrl-click-goto-def? |
@isc30 It's a holiday weekend for the US and much (most) of the team is not working until Tuesday. I'll leave a couple notes for people to make sure we aren't any later than that in replying. |
@isc30 @CyrusNajmabadi NavigableSymbolsTest is the right place to add tests that specifically test ctrl-click gtd. Looks like some were added there. |
Hi @rchande, after some investigation it looks like |
@isc30 What doesn't work? |
@rchande in |
@isc30 I haven't looked at this code in a long time, can you share your specific test failure message? thanks! |
The test cases failed because it resolves IDocumentNavigationService to DefaultSymbolNavigationService instead of MockDocumentNavigationService at runtime. MockDocumentNavigationService.TryNavigateToSymbol() always return true while DefaultSymbolNavigationService.TryNavigateToSymbol() always return false. I need to check with @sharwell to see if we need special symbol like [|symbol definition|] in the workspace document in these test cases for the tests to resolve to right DocumentNavigationService . I will get back to this thread tomorrow. |
That's a nice observation, I just updated the tests to explicitly specify the target symbol. They seem to still fail, but they look better now |
I took a quick look, the workspace still resolves IDocumentNavigationService to DefaultSymbolNavigationService even after the target symbol is explicitly specified. |
I have a question. When I first came across this issue I thought the underlying problem was that the ctrl+click navigation actually considers the type of the expression as a symbol to navigate to. That doesn't seem like an intuitive behavior to me given that most of the time when I use that to navigate to a symbol, it's usually a declaration or a variable, not its type. So when I approached this issue and tried to fix it, instead of having |
@Neme12 yep, I did the same thing in the first version of this PR but we reduced the scope of the changes to only literals by removing the heuristic that tried to guess the "literality" without knowing the token itself.
This fixes the problem described in the ticket, but it can have side-effects. The truth is that we wanted to maintain the navigation to tokens that don't have a symbol but have a type.
Not sure, maybe @CyrusNajmabadi can throw a light on this |
Fair enough, if that's what the team wants (to special case this for literals). I considered the fact that this navigates to the type of an expression at all to be undesirable. It's not what I would expect personally. |
I can't think of anything. Note: i would be ok with this being written as an 'includeType' flag. My major critique before was that hte flag was called "includeLiterals" but wasn't actually checking for literal-ness. |
We've commonly had people use go-to-def on things like strings so that they can easily see the entire System.String api. That's why go-to-def works on literals. |
Yes I understand (that's why the flag). I think that go to definition is a lot more "explicit" action so it's OK for it to also navigate to the type, but when holding ctrl and hovering over literals, it probably shouldn't be offered - in this case it's a lot more distracting. Seeing something underlined makes me think there's an actual definition for the value. I wouldn't expect that to be offered just for the type. I can't think of anything either. A single token that is an expression with a type, doesn't refer to a symbol and also not a literal? |
Good to know, I focused on literals because that's what the exact ticket suggests but, to be honest, I prefer not changing the Maybe I can change it to the simple flag and leave a comment in the heuristic: "if a token has no aliased, referenced or declared symbols (mostly literals) we don't want to enable Ctrl+Click on them". What do you think? 👍 yep |
@isc30 Sorry I'm slow getting to this review. I've been tied up with some performance issues but I should get this reviewed tonight or tomorrow morning. 😄 |
Thanks @sharwell, please don't spend too much time reviewing the implementation as I will refactor it in a short time. It would be very helpful if you could investigate the failing tests as I have no idea about the reasons |
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 implementation approach seems to make sense to me. It's getting late here so I'll review the unit tests tomorrow. 😄
{ | ||
var syntaxTree = semanticModel.SyntaxTree; | ||
var syntaxFacts = workspace.Services.GetLanguageServices(semanticModel.Language).GetService<ISyntaxFactsService>(); | ||
var token = await syntaxTree.GetTouchingTokenAsync(position, syntaxFacts.IsBindableToken, cancellationToken, findInsideTrivia: true).ConfigureAwait(false); |
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 can be changed to simply return the result of GetTouchingTokenAsync
directly, and remove the async
modifier from the GetTokenAtPositionAsync
method. This change will avoid the creation of a new async state machine and Task<T>
instance on this code path.
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 like something that should be optimized by the compiler
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.
@sharwell what about having an analyzer for this?
@@ -15,7 +15,7 @@ internal abstract class AbstractGoToSymbolService : ForegroundThreadAffinitizedO | |||
var cancellationToken = context.CancellationToken; | |||
|
|||
var service = document.GetLanguageService<IGoToDefinitionSymbolService>(); | |||
var (symbol, span) = await service.GetSymbolAndBoundSpanAsync(document, position, cancellationToken).ConfigureAwait(false); | |||
var (symbol, span) = await service.GetSymbolAndBoundSpanAsync(document, position, includeLiterals: false, cancellationToken).ConfigureAwait(false); |
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's not obvious to me why AbstractGoToSymbolService
and AbstractGoToDefinitionService
are different. A comment on the types would be helpful for future readers.
❓ Why refactor? One thing I liked about this is how the changes didn't impact very many lines of non-test code. If the refactoring makes the review even simpler, then go for it. However, if this is a larger effort to consolidate services where the resulting code is simple but the diff is large, I would recommend waiting and sending that as a separate pull request. |
The idea is to ignore all the tokens that have no symbols instead of only literals. This avoids the need for the flag 'IsLiteral' in 'SemanticInfo' and we can rely just on the "has related symbol or not" for Ctrl+Click
It will be very similar to my first solution and reduce the scope of the changes.
|
Looks like all tests are passing after @JieCarolHu's changes, thanks a lot to everyone! 😄 |
No idea why |
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.
Need to update the test MEF export but otherwise looks good
Imports Microsoft.CodeAnalysis.Navigation | ||
|
||
Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers | ||
<ExportWorkspaceServiceFactory(GetType(ISymbolNavigationService), ServiceLayer.Editor), [Shared]> |
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.
❗️ Needs the following:
- Use
ServiceLayer.Test
- Add
<PartNotDiscoverable>
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.
➡️ ServiceLayer.Test
isn't a thing, so ServiceLayer.Host
was used instead.
@jinujoseph Requesting approval to merge |
Approved to merge for 15.8.Preview2 |
test windows_debug_spanish_unit32_prtest |
@isc30 Thanks for your contribution here and patience while we figured out the unexpected issues that arose. 😄 |
Thanks a lot to everyone, I really enjoyed doing the contribution! |
📝 By @sharwell: These tests now pass per #24910 (comment)
Bugs this fixes
#23030
Root cause analysis
Added tests for both C# and VB covering SymbolNavigation and GoToDefinition for literals