diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index 474b952c478..5b81796d8d5 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -46,6 +46,7 @@ + diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs index 56c074d78b2..f3212db489f 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs @@ -26,6 +26,8 @@ type internal FSharpCheckerProvider settings: EditorOptions ) = + let metadataAsSource = FSharpMetadataAsSourceService() + let tryGetMetadataSnapshot (path, timeStamp) = try let md = Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetMetadata(workspace, path, timeStamp) @@ -85,3 +87,5 @@ type internal FSharpCheckerProvider member this.Checker = checker.Value + member _.MetadataAsSource = metadataAsSource + diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs index a35323f9de2..c077def059a 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs @@ -433,3 +433,5 @@ type internal FSharpProjectOptionsManager reactor.SetCpsCommandLineOptions(projectId, sourcePaths, options.ToArray()) member _.Checker = checkerProvider.Checker + + member _.MetadataAsSource = checkerProvider.MetadataAsSource diff --git a/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs new file mode 100644 index 00000000000..b1e0df38153 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.Editor + +open System +open System.Threading +open System.Collections.Immutable +open System.Diagnostics +open System.IO +open System.Linq +open System.Text +open System.Runtime.InteropServices +open System.Reflection.PortableExecutable + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.FindSymbols +open Microsoft.CodeAnalysis.Text +open Microsoft.CodeAnalysis.Navigation +open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation +open Microsoft.VisualStudio.ComponentModelHost + +open Microsoft.VisualStudio +open Microsoft.VisualStudio.Editor +open Microsoft.VisualStudio.Threading +open Microsoft.VisualStudio.Shell +open Microsoft.VisualStudio.Shell.Interop +open Microsoft.VisualStudio.TextManager.Interop + +open FSharp.Compiler.SourceCodeServices +open FSharp.Compiler.Text + +module internal MetadataAsSource = + + open Microsoft.CodeAnalysis.CSharp + open ICSharpCode.Decompiler + open ICSharpCode.Decompiler.CSharp + open ICSharpCode.Decompiler.Metadata + open ICSharpCode.Decompiler.CSharp.Transforms + open ICSharpCode.Decompiler.TypeSystem + + let generateTemporaryCSharpDocument (asmIdentity: AssemblyIdentity, name: string, metadataReferences) = + let rootPath = Path.Combine(Path.GetTempPath(), "MetadataAsSource") + let extension = ".cs" + let directoryName = Guid.NewGuid().ToString("N") + let temporaryFilePath = Path.Combine(rootPath, directoryName, name + extension) + + let projectId = ProjectId.CreateNewId() + + let parseOptions = CSharpParseOptions.Default.WithLanguageVersion(Microsoft.CodeAnalysis.CSharp.LanguageVersion.Preview) + // Just say it's always a DLL since we probably won't have a Main method + let compilationOptions = Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + + // We need to include the version information of the assembly so InternalsVisibleTo and stuff works + let assemblyInfoDocumentId = DocumentId.CreateNewId(projectId) + let assemblyInfoFileName = "AssemblyInfo" + extension + let assemblyInfoString = String.Format(@"[assembly: System.Reflection.AssemblyVersion(""{0}"")]", asmIdentity.Version) + + let assemblyInfoSourceTextContainer = SourceText.From(assemblyInfoString, Encoding.UTF8).Container + + let assemblyInfoDocument = + DocumentInfo.Create( + assemblyInfoDocumentId, + assemblyInfoFileName, + loader = TextLoader.From(assemblyInfoSourceTextContainer, VersionStamp.Default)) + + let generatedDocumentId = DocumentId.CreateNewId(projectId) + let documentInfo = + DocumentInfo.Create( + generatedDocumentId, + Path.GetFileName(temporaryFilePath), + filePath = temporaryFilePath, + loader = FileTextLoader(temporaryFilePath, Encoding.UTF8)) + + let projectInfo = + ProjectInfo.Create( + projectId, + VersionStamp.Default, + name = asmIdentity.Name, + assemblyName = asmIdentity.Name, + language = LanguageNames.CSharp, + compilationOptions = compilationOptions, + parseOptions = parseOptions, + documents = [|assemblyInfoDocument;documentInfo|], + metadataReferences = metadataReferences) + + (projectInfo, documentInfo) + + let decompileCSharp (symbolFullTypeName: string, assemblyLocation: string) = + let logger = new StringBuilder() + + // Initialize a decompiler with default settings. + let decompiler = CSharpDecompiler(assemblyLocation, DecompilerSettings()) + // Escape invalid identifiers to prevent Roslyn from failing to parse the generated code. + // (This happens for example, when there is compiler-generated code that is not yet recognized/transformed by the decompiler.) + decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers()) + + let fullTypeName = FullTypeName(symbolFullTypeName) + + // Try to decompile; if an exception is thrown the caller will handle it + let text = decompiler.DecompileTypeAsString(fullTypeName) + + let text = text + "#if false // " + Environment.NewLine + let text = text + logger.ToString() + let text = text + "#endif" + Environment.NewLine + + SourceText.From(text) + + let showDocument (filePath, name, serviceProvider: IServiceProvider) = + let vsRunningDocumentTable4 = serviceProvider.GetService() + let fileAlreadyOpen = vsRunningDocumentTable4.IsMonikerValid(filePath) + + let openDocumentService = serviceProvider.GetService() + + let (_, _, _, _, windowFrame) = openDocumentService.OpenDocumentViaProject(filePath, ref VSConstants.LOGVIEWID.TextView_guid) + + let componentModel = serviceProvider.GetService() + let editorAdaptersFactory = componentModel.GetService(); + let documentCookie = vsRunningDocumentTable4.GetDocumentCookie(filePath) + let vsTextBuffer = vsRunningDocumentTable4.GetDocumentData(documentCookie) :?> IVsTextBuffer + let textBuffer = editorAdaptersFactory.GetDataBuffer(vsTextBuffer) + + if not fileAlreadyOpen then + ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_IsProvisional, true)) |> ignore + ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_OverrideCaption, name)) |> ignore + ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_OverrideToolTip, name)) |> ignore + + windowFrame.Show() |> ignore + + let textContainer = textBuffer.AsTextContainer() + let mutable workspace = Unchecked.defaultof<_> + if Workspace.TryGetWorkspace(textContainer, &workspace) then + let solution = workspace.CurrentSolution + let documentId = workspace.GetDocumentIdInCurrentContext(textContainer) + match box documentId with + | null -> None + | _ -> solution.GetDocument(documentId) |> Some + else + None + +[] +type internal FSharpMetadataAsSourceService() = + + member val CSharpFiles = System.Collections.Concurrent.ConcurrentDictionary(StringComparer.OrdinalIgnoreCase) + + member this.ShowCSharpDocument(projInfo: ProjectInfo, docInfo: DocumentInfo, text: Text.SourceText) = + let _ = + let directoryName = Path.GetDirectoryName(docInfo.FilePath) + if Directory.Exists(directoryName) |> not then + Directory.CreateDirectory(directoryName) |> ignore + use fileStream = new FileStream(docInfo.FilePath, IO.FileMode.Create) + use writer = new StreamWriter(fileStream) + text.Write(writer) + + this.CSharpFiles.[docInfo.FilePath] <- (projInfo, docInfo) + + MetadataAsSource.showDocument(docInfo.FilePath, docInfo.Name, ServiceProvider.GlobalProvider) \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs b/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs index 5947c59b8ae..0e565801fd3 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs @@ -33,11 +33,33 @@ type internal SingleFileWorkspaceMap(workspace: VisualStudioWorkspace, projectContext.AddSourceFile(filePath, sourceCodeKind = createSourceCodeKind filePath) projectContext + let createCSharpMetadataProjectContext (projInfo: ProjectInfo) (docInfo: DocumentInfo) = + let projectContext = projectContextFactory.CreateProjectContext(LanguageNames.CSharp, projInfo.Id.ToString(), projInfo.FilePath, Guid.NewGuid(), null, null) + projectContext.DisplayName <- projInfo.Name + projectContext.AddSourceFile(docInfo.FilePath, sourceCodeKind = SourceCodeKind.Regular) + + for metaRef in projInfo.MetadataReferences do + match metaRef with + | :? PortableExecutableReference as peRef -> + projectContext.AddMetadataReference(peRef.FilePath, MetadataReferenceProperties.Assembly) + | _ -> + () + + projectContext + do miscFilesWorkspace.DocumentOpened.Add(fun args -> let document = args.Document + if document.Project.Language = FSharpConstants.FSharpLanguageName && workspace.CurrentSolution.GetDocumentIdsWithFilePath(document.FilePath).Length = 0 then files.[document.FilePath] <- createProjectContext document.FilePath + + if optionsManager.MetadataAsSource.CSharpFiles.ContainsKey(document.FilePath) && workspace.CurrentSolution.GetDocumentIdsWithFilePath(document.FilePath).Length = 0 then + match optionsManager.MetadataAsSource.CSharpFiles.TryGetValue(document.FilePath) with + | true, (projInfo, docInfo) -> + files.[document.FilePath] <- createCSharpMetadataProjectContext projInfo docInfo + | _ -> + () ) workspace.DocumentOpened.Add(fun args -> @@ -57,6 +79,8 @@ type internal SingleFileWorkspaceMap(workspace: VisualStudioWorkspace, optionsManager.ClearSingleFileOptionsCache(document.Id) projectContext.Dispose() | _ -> () + + optionsManager.MetadataAsSource.CSharpFiles.TryRemove(document.FilePath) |> ignore ) do diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index 00b8152934f..d5166440213 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -8,19 +8,28 @@ open System.Collections.Immutable open System.Diagnostics open System.IO open System.Linq +open System.Text open System.Runtime.InteropServices +open System.Reflection.PortableExecutable open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.FindSymbols open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Navigation open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation +open Microsoft.VisualStudio.ComponentModelHost +open Microsoft.VisualStudio +open Microsoft.VisualStudio.Editor +open Microsoft.VisualStudio.Threading +open Microsoft.VisualStudio.Shell open Microsoft.VisualStudio.Shell.Interop +open Microsoft.VisualStudio.TextManager.Interop open FSharp.Compiler.SourceCodeServices open FSharp.Compiler.Text + module private Symbol = let fullName (root: ISymbol) : string = let rec inner parts (sym: ISymbol) = @@ -145,6 +154,11 @@ type internal StatusBar(statusBar: IVsStatusbar) = type internal FSharpGoToDefinitionNavigableItem(document, sourceSpan) = inherit FSharpNavigableItem(Glyph.BasicFile, ImmutableArray.Empty, document, sourceSpan) +[] +type internal FSharpGoToDefinitionResult = + | NavigableItem of FSharpNavigableItem + | ExternalAssembly of ProjectInfo * DocumentInfo * FSharpSymbolUse * FSharpExternalSymbol + type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpProjectOptionsManager) = let userOpName = "GoToDefinition" @@ -241,23 +255,28 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP match declarations with | FSharpFindDeclResult.ExternalDecl (assembly, targetExternalSym) -> - let! project = originDocument.Project.Solution.Projects |> Seq.tryFind (fun p -> p.AssemblyName.Equals(assembly, StringComparison.OrdinalIgnoreCase)) - let! symbols = SymbolFinder.FindSourceDeclarationsAsync(project, fun _ -> true) - - let roslynSymbols = - symbols - |> Seq.collect ExternalSymbol.ofRoslynSymbol - |> Array.ofSeq - - let! symbol = - roslynSymbols - |> Seq.tryPick (fun (sym, externalSym) -> - if externalSym = targetExternalSym then Some sym - else None - ) - - let! location = symbol.Locations |> Seq.tryHead - return (FSharpGoToDefinitionNavigableItem(project.GetDocument(location.SourceTree), location.SourceSpan), idRange) + let projectOpt = originDocument.Project.Solution.Projects |> Seq.tryFind (fun p -> p.AssemblyName.Equals(assembly, StringComparison.OrdinalIgnoreCase)) + match projectOpt with + | Some project -> + let! symbols = SymbolFinder.FindSourceDeclarationsAsync(project, fun _ -> true) + + let roslynSymbols = + symbols + |> Seq.collect ExternalSymbol.ofRoslynSymbol + |> Array.ofSeq + + let! symbol = + roslynSymbols + |> Seq.tryPick (fun (sym, externalSym) -> + if externalSym = targetExternalSym then Some sym + else None + ) + + let! location = symbol.Locations |> Seq.tryHead + return (FSharpGoToDefinitionResult.NavigableItem(FSharpGoToDefinitionNavigableItem(project.GetDocument(location.SourceTree), location.SourceSpan)), idRange) + | _ -> + let tmpProjInfo, tmpDocId = MetadataAsSource.generateTemporaryCSharpDocument(AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName), targetSymbolUse.Symbol.DisplayName, originDocument.Project.MetadataReferences) + return (FSharpGoToDefinitionResult.ExternalAssembly(tmpProjInfo, tmpDocId, targetSymbolUse, targetExternalSym), idRange) | FSharpFindDeclResult.DeclFound targetRange -> // if goto definition is called at we are alread at the declaration location of a symbol in @@ -275,7 +294,7 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange) let navItem = FSharpGoToDefinitionNavigableItem (implDocument, implTextSpan) - return (navItem, idRange) + return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) else // jump from implementation to the corresponding signature let declarations = checkFileResults.GetDeclarationLocation (fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLineString, lexerSymbol.FullIsland, true) match declarations with @@ -284,7 +303,7 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP let! sigSourceText = sigDocument.GetTextAsync () |> liftTaskAsync let! sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sigSourceText, targetRange) let navItem = FSharpGoToDefinitionNavigableItem (sigDocument, sigTextSpan) - return (navItem, idRange) + return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) | _ -> return! None // when the target range is different follow the navigation convention of @@ -297,7 +316,7 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP // if the gotodef call originated from a signature and the returned target is a signature, navigate there if isSignatureFile targetRange.FileName && preferSignature then let navItem = FSharpGoToDefinitionNavigableItem (sigDocument, sigTextSpan) - return (navItem, idRange) + return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) else // we need to get an FSharpSymbol from the targetRange found in the signature // that symbol will be used to find the destination in the corresponding implementation file let implFilePath = @@ -314,7 +333,7 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange) let navItem = FSharpGoToDefinitionNavigableItem (implDocument, implTextSpan) - return (navItem, idRange) + return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) | _ -> return! None } @@ -330,8 +349,7 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP member this.FindDefinitionsForPeekTask(originDocument: Document, position: int, cancellationToken: CancellationToken) = this.FindDefinitionAtPosition(originDocument, position) |> Async.map ( - Option.map (fun (navItem, _) -> navItem :> FSharpNavigableItem) - >> Option.toArray + Option.toArray >> Array.toSeq) |> RoslynHelpers.StartAsyncAsTask cancellationToken @@ -343,7 +361,7 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP /// Navigate to the positon of the textSpan in the provided document /// used by quickinfo link navigation when the tooltip contains the correct destination range. - member _.TryNavigateToTextSpan(document: Document, textSpan: TextSpan, statusBar: StatusBar) = + member _.TryNavigateToTextSpan(document: Document, textSpan: Microsoft.CodeAnalysis.Text.TextSpan, statusBar: StatusBar) = let navigableItem = FSharpGoToDefinitionNavigableItem(document, textSpan) let workspace = document.Project.Solution.Workspace let navigationService = workspace.Services.GetService() diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs index e8bb0d1fdc9..ba377a3929a 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs @@ -2,18 +2,24 @@ namespace Microsoft.VisualStudio.FSharp.Editor +open System open System.Composition +open System.IO open System.Threading open System.Threading.Tasks open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Editor open Microsoft.CodeAnalysis.Host.Mef open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor +open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation open Microsoft.VisualStudio.Shell open Microsoft.VisualStudio.Shell.Interop -open System +open Microsoft.VisualStudio.LanguageServices + +open FSharp.Compiler.SourceCodeServices [)>] [)>] @@ -25,12 +31,22 @@ type internal FSharpGoToDefinitionService ) = let gtd = GoToDefinition(checkerProvider.Checker, projectInfoManager) - let statusBar = StatusBar(ServiceProvider.GlobalProvider.GetService()) + let statusBar = StatusBar(ServiceProvider.GlobalProvider.GetService()) + let metadataAsSourceService = checkerProvider.MetadataAsSource interface IFSharpGoToDefinitionService with /// Invoked with Peek Definition. member _.FindDefinitionsAsync (document: Document, position: int, cancellationToken: CancellationToken) = - gtd.FindDefinitionsForPeekTask(document, position, cancellationToken) + let task = gtd.FindDefinitionsForPeekTask(document, position, cancellationToken) + task.Wait(cancellationToken) + let results = task.Result + results + |> Seq.choose(fun (result, _) -> + match result with + | FSharpGoToDefinitionResult.NavigableItem(navItem) -> Some navItem + | _ -> None + ) + |> Task.FromResult /// Invoked with Go to Definition. /// Try to navigate to the definiton of the symbol at the symbolRange in the originDocument @@ -44,13 +60,44 @@ type internal FSharpGoToDefinitionService // Task.Wait throws an exception if the task is cancelled, so be sure to catch it. try // This call to Wait() is fine because we want to be able to provide the error message in the status bar. - gtdTask.Wait() + gtdTask.Wait(cancellationToken) if gtdTask.Status = TaskStatus.RanToCompletion && gtdTask.Result.IsSome then - let item, _ = gtdTask.Result.Value - gtd.NavigateToItem(item, statusBar) + let result, _ = gtdTask.Result.Value + match result with + | FSharpGoToDefinitionResult.NavigableItem(navItem) -> + gtd.NavigateToItem(navItem, statusBar) + // 'true' means do it, like Sheev Palpatine would want us to. + true + | FSharpGoToDefinitionResult.ExternalAssembly(tmpProjInfo, tmpDocInfo, targetSymbolUse, targetExternalSymbol) -> + match targetSymbolUse.Symbol.Assembly.FileName with + | Some targetSymbolAssemblyFileName -> + try + let symbolFullTypeName = + match targetExternalSymbol with + | FSharpExternalSymbol.Constructor(tyName, _) + | FSharpExternalSymbol.Event(tyName, _) + | FSharpExternalSymbol.Field(tyName, _) + | FSharpExternalSymbol.Method(tyName, _, _, _) + | FSharpExternalSymbol.Property(tyName, _) + | FSharpExternalSymbol.Type(tyName) -> tyName - // 'true' means do it, like Sheev Palpatine would want us to. - true + let text = MetadataAsSource.decompileCSharp(symbolFullTypeName, targetSymbolAssemblyFileName) + let tmpShownDocOpt = metadataAsSourceService.ShowCSharpDocument(tmpProjInfo, tmpDocInfo, text) + match tmpShownDocOpt with + | Some tmpShownDoc -> + let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, TextSpan()) + gtd.NavigateToItem(navItem, statusBar) + true + | _ -> + statusBar.TempMessage (SR.CannotDetermineSymbol()) + false + with + | _ -> + statusBar.TempMessage (SR.CannotDetermineSymbol()) + false + | _ -> + statusBar.TempMessage (SR.CannotDetermineSymbol()) + false else statusBar.TempMessage (SR.CannotDetermineSymbol()) false diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs index 2c7b1b7a894..941a9c30e6e 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs @@ -58,13 +58,17 @@ type internal FSharpNavigableSymbolSource(checkerProvider: FSharpCheckerProvider statusBar.Clear() if gtdTask.Status = TaskStatus.RanToCompletion && gtdTask.Result.IsSome then - let navigableItem, range = gtdTask.Result.Value + let result, range = gtdTask.Result.Value let declarationTextSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, range) let declarationSpan = Span(declarationTextSpan.Start, declarationTextSpan.Length) let symbolSpan = SnapshotSpan(snapshot, declarationSpan) - return FSharpNavigableSymbol(navigableItem, symbolSpan, gtd, statusBar) :> INavigableSymbol + match result with + | FSharpGoToDefinitionResult.NavigableItem(navItem) -> + return FSharpNavigableSymbol(navItem, symbolSpan, gtd, statusBar) :> INavigableSymbol + | _ -> + return null else statusBar.TempMessage(SR.CannotDetermineSymbol())