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

[#23030] Disable literal Ctrl+Click navigation #24910

Merged
merged 12 commits into from
May 2, 2018

Conversation

isc30
Copy link
Contributor

@isc30 isc30 commented Feb 17, 2018

📝 By @sharwell: These tests now pass per #24910 (comment)

  • TestCSharpLiteralGoToDefinition
  • TestCSharpStringLiteralGoToDefinition
  • TestVisualBasicLiteralGoToDefinition
  • TestVisualBasicStringLiteralGoToDefinition

Bugs this fixes

#23030

Root cause analysis

Added tests for both C# and VB covering SymbolNavigation and GoToDefinition for literals

@isc30 isc30 requested a review from a team as a code owner February 17, 2018 04:30
@dnfclas
Copy link

dnfclas commented Feb 17, 2018

CLA assistant check
All CLA requirements met.


return includeLiterals
? (symbol ?? semanticInfo.Type)
: symbol;
Copy link
Member

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.

Copy link
Member

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.

Copy link
Contributor Author

@isc30 isc30 Feb 17, 2018

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

Copy link
Member

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).

Copy link
Member

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!

Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Feb 17, 2018

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! :)

Copy link
Contributor Author

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 👍

Copy link
Contributor Author

@isc30 isc30 Feb 17, 2018

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

Copy link
Member

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".

@CyrusNajmabadi
Copy link
Member

I need some assistance to know if I'm going in the proper direction and some clue about why those tests are failing (with manual testing the cases are working well)

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?

@sharwell
Copy link
Member

@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.

@sharwell sharwell added the Community The pull request was submitted by a contributor who is not a Microsoft employee. label Feb 17, 2018
@rchande
Copy link
Contributor

rchande commented Feb 20, 2018

@isc30 @CyrusNajmabadi NavigableSymbolsTest is the right place to add tests that specifically test ctrl-click gtd. Looks like some were added there.

@isc30
Copy link
Contributor Author

isc30 commented Feb 20, 2018

Hi @rchande, after some investigation it looks like Test method in GoToDefinitionTests.vb doesn't work properly for built-in types (int, string in my tests)

@rchande
Copy link
Contributor

rchande commented Feb 20, 2018

@isc30 What doesn't work?

@isc30
Copy link
Contributor Author

isc30 commented Feb 20, 2018

@rchande in GoToDefinitionTests.vb, symbol navigation (using goto-definition) is being checked inside the Test method. As far as I have seen, it works well for user-defined types but not for built-in ones (int, string, etc). This makes my tests for literals fail. Please correct me if I'm wrong

@rchande
Copy link
Contributor

rchande commented Feb 20, 2018

@isc30 I haven't looked at this code in a long time, can you share your specific test failure message? thanks!

@JieCarolHu
Copy link
Contributor

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.

@isc30
Copy link
Contributor Author

isc30 commented Feb 21, 2018

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

@JieCarolHu
Copy link
Contributor

I took a quick look, the workspace still resolves IDocumentNavigationService to DefaultSymbolNavigationService even after the target symbol is explicitly specified.
@sharwell Could you confirm if this following way is correct to explicitly specify the target symbol?
[|int|] x = 1$$23;

@Neme12
Copy link
Contributor

Neme12 commented Feb 21, 2018

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 bool includeLiterals, I had bool includeType in GetSymbolAndBoundSpanAsync. What things other than literals might that affect? Would that be an acceptable solution? It seems a lot simpler to me but more importantly, it seems to me like that's the underlying problem here - when I click on an expression I don't expect it to take me to its type. The fact that this affects literals seemed to me like just a symptom of the real issue.

@isc30
Copy link
Contributor Author

isc30 commented Feb 21, 2018

instead of having bool includeLiterals, I had bool includeType in GetSymbolAndBoundSpanAsync

@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.

Would that be an acceptable solution?

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.

What things other than literals would that affect?

Not sure, maybe @CyrusNajmabadi can throw a light on this

@Neme12
Copy link
Contributor

Neme12 commented Feb 21, 2018

The truth is that we wanted to maintain the navigation to tokens that don't have a symbol but have a type

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.

@CyrusNajmabadi
Copy link
Member

Not sure, maybe @CyrusNajmabadi can throw a light on this

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.

@CyrusNajmabadi
Copy link
Member

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.

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.

@Neme12
Copy link
Contributor

Neme12 commented Feb 21, 2018

@CyrusNajmabadi

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?

@isc30
Copy link
Contributor Author

isc30 commented Feb 21, 2018

Good to know, I focused on literals because that's what the exact ticket suggests but, to be honest, I prefer not changing the SemanticInfo etc but having the flag for including the type or not (like in the first version of the PR). I cannot think of any situation that will end up using the Symbol.Type apart from literals.

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
👎 nope

@sharwell
Copy link
Member

@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. 😄

@isc30
Copy link
Contributor Author

isc30 commented Feb 22, 2018

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

Copy link
Member

@sharwell sharwell left a 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);
Copy link
Member

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.

Copy link
Contributor

@Neme12 Neme12 Feb 22, 2018

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

Copy link
Contributor

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

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.

@sharwell
Copy link
Member

as I will refactor it in a short time.

❓ 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.

@isc30
Copy link
Contributor Author

isc30 commented Feb 22, 2018 via email

@jcouv jcouv added the Area-IDE label Mar 31, 2018
@JieCarolHu
Copy link
Contributor

@isc30 I merged the latest master and created MockSymbolNavigationService for your literal go to definition tests, I created a PR here and it is working. Feel free to merge it back to your branch. Thanks :)
#26096

@isc30
Copy link
Contributor Author

isc30 commented Apr 11, 2018

Looks like all tests are passing after @JieCarolHu's changes, thanks a lot to everyone! 😄

@isc30
Copy link
Contributor Author

isc30 commented Apr 26, 2018

No idea why windows_release_vs-integration_prtest is failing, am I missing something in the PR?

Copy link
Member

@sharwell sharwell left a 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]>
Copy link
Member

Choose a reason for hiding this comment

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

❗️ Needs the following:

  1. Use ServiceLayer.Test
  2. Add <PartNotDiscoverable>

Copy link
Member

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.

@sharwell
Copy link
Member

sharwell commented May 1, 2018

@jinujoseph Requesting approval to merge

@jinujoseph
Copy link
Contributor

Approved to merge for 15.8.Preview2

@jinujoseph
Copy link
Contributor

test windows_debug_spanish_unit32_prtest

@sharwell sharwell merged commit 3adcd5e into dotnet:master May 2, 2018
@sharwell sharwell added this to the 15.8 milestone May 2, 2018
@sharwell
Copy link
Member

sharwell commented May 2, 2018

@isc30 Thanks for your contribution here and patience while we figured out the unexpected issues that arose. 😄

@isc30
Copy link
Contributor Author

isc30 commented May 8, 2018

Thanks a lot to everyone, I really enjoyed doing the contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Approved to merge Area-IDE Community The pull request was submitted by a contributor who is not a Microsoft employee.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants