dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.87k stars 779 forks source link

Go To Definition: C# to F# #3497

Closed iainnicol closed 1 year ago

iainnicol commented 7 years ago

In #3357 there was awesome work so that Visual Studio's Go To Definition (F12) can work from F# to C#.

It would be treat if this worked the other way, from C# to F#.

Where do we think we need to hook into Roslyn to enable this?

vasily-kirichenko commented 7 years ago

There was a discussion about it. It turned out Roslyn must use SVsSymbolicNavigationManager so we could hook into it, read the full discussion here https://github.com/Microsoft/visualfsharp/pull/3357#issuecomment-317261778

jannesiera commented 5 years ago

Checking in about this at github/roslyn it seems they are unwilling to do this.

https://github.com/dotnet/roslyn/issues/8105

How could we move this forward? What alternatives are there?

cartermp commented 5 years ago

@jannesiera My read of that issue is that there isn't a strong appetite to use those older APIs, but I wouldn't rule out a contribution to Roslyn that uses them.

jannesiera commented 5 years ago

Over at dotnet/roslyn#8105 there was mention of introducing a MEF interface so we could intregrate with Roslyn to get this to work. It would look something like this:

public interface ISymbolicNavigationService { Task<bool> TryNavigateToSymbol(ISymbol symbol, CancellationToken cancellationToken); Task<bool> TryNavigateToSymbol(string rqName, CancellationToken cancellationToken); }

From Roslyn's perspective would work something like this (quoting @CyrusNajmabadi):

This API would just be about the language saying "i can take it from here". i.e. in an async-cancellable manner, it would introspect itself and say "yup! that looks like a symbol from my language (or not)". And it woudl return true/false accordingly. If 'false', nothing left for it to do. If 'true', it would take over and actually do the navigation however it wanted.

Over the weekend I was able to set up a local testing environment and, after providing an implementation of ISymbolicNavigationService on our side, I could trigger TryNavigateToSymbol after opening a C# projecting pressing F12 on a function provided by a referenced F# project.

I'm not sure what would be the best way to proceed from here on, being unfamiliar with the internals of visualfsharp. At first sight it seems trying to guess which FSharpSymbol the provided ISymbol might represent and then trying to find that FSharpSymbol, and navigate to it if found.

Some guidance here would be very welcome.

Tagging some people that worked on the initial issue: @vasily-kirichenko @dsyme @saul

vasily-kirichenko commented 5 years ago

@jannesiera What about me, I've not used Visual Studio for a couple of years and I hope won't ever use it (I switched to Rider where this feature has worked from the beginning).

cartermp commented 5 years ago

@jannesiera Nice work! The work on the F# side (at a high level) is the following:

The first bit is likely going to involve some experimentation, since ISymbol is not a concept that the F# tooling understands. There may be some work to convert data from there to data the F# compiler service can understand, and the usual juggling of existing APIs - look in QuickInfo or NavigableSymbols for examples. If you need to construct some artificial Roslyn objects to use them, that shouldn't be too difficult.

If it's not possible to use existing APIs, you may need to add a new one to the compiler service. Something like, "given a fully qualified symbol name, give me that symbol". That may get more difficult to do efficiently, since the naiive implementation would be proportional to Find All References in terms of the work it does.

CyrusNajmabadi commented 5 years ago

Do you guys support rqnames?

jannesiera commented 5 years ago

@cartermp

I got a basic PoC working (at least for very simple declarations, like module and function references), but:

Use information from an ISymbol to find the correct F# symbol definition, in a way that doesn't require calling GetAllUsesOfAllSymbols. There may be usable be info about the project and namespace that a symbol is declared in that could be used here.

Currently I'm doing this procedure:

  1. Receive ISymbol (roslynSymbol) from Roslyn.
  2. Inspect which assembly the roslynSymbol and look through the projects in the current solution if a project happens to have the same name as the assembly.
  3. Parse the whole project.
  4. Call GetAllUsesOfAllSymbols on the parsed project.
  5. Go though all the symbols, and try to match them (very naively) on their FullName (and retain only symbol uses that are definitions).
  6. (If any found) Get the symbol at the head, take its range and convert to NavigateToItem.
  7. Navigate to the symbol.

This is (1) obviously not very fast and (2) I'm unsure about my strategy of matching the roslynSymbol to an appropriate fsharpSymbol.

Regarding (1) I do have the namespace available. How could I use this to check less files? Also, is there a way I could avoid parsing the project with FSharpChecker's ParseAndCheckProject? Is there an in-memory database of all known symbols indexed by namespace or something alike (or is it naive to think like this)?

cartermp commented 5 years ago

@jannesiera I'd say at this point, this sounds good enough to submit a PR for that can be reviewed.

Generally speaking, symbols should be cached so it may not be too slow to scan GetAllUsesOfAllSymbols for a single project, but we'd have to use it on real codebases to really know for sure. If the time spent doing this work is proportional to the time for any other operation that requires a call to ParseAndCheckProject then it should be okay. Optimizing this sounds like it would imply a much larger change to the compiler service. There are a great deal of things we'd like to do (e.g., move to a snapshot model akin to Roslyn, implement some speculative model for understanding if cross-project references have semantic changes in them, etc.).

As far as mapping an ISymbol into an F# symbol goes, I think once you have the fully qualified name it should be enough to be correct. If you submit a PR we can establish a thorough test plan to validate that it works.

My main concern in light of this is making sure that we do everything right at the Roslyn <--> F# boundary. To answer @CyrusNajmabadi's question, I don't think we support rqnames, no. It certainly doesn't show up in our codebase since we plug into Roslyn or newer editor APIs directly.

dsyme commented 1 year ago

Duplicate