diff --git a/src/fsharp/NicePrint.fs b/src/fsharp/NicePrint.fs index a91660a6bf4..b8f49aa4553 100755 --- a/src/fsharp/NicePrint.fs +++ b/src/fsharp/NicePrint.fs @@ -1424,7 +1424,7 @@ module private TastDefinitionPrinting = let nameL = eventTag |> wordL let typL = layoutType denv (e.GetDelegateType(amap, m)) - staticL ^^ WordL.keywordEvent ^^ nameL ^^ WordL.colon ^^ typL + staticL ^^ WordL.keywordMember ^^ nameL ^^ WordL.colon ^^ typL let private layoutPropInfo denv amap m (p: PropInfo) = match p.ArbitraryValRef with @@ -1783,6 +1783,17 @@ module private TastDefinitionPrinting = let xs = List.map (layoutTycon denv infoReader ad m false (wordL (tagKeyword "and"))) t aboveListL (x :: xs) + let layoutEntity (denv: DisplayEnv) (infoReader: InfoReader) ad m (entity: Entity) = + if entity.IsModule then + // TODO: Implementation of layoutTycon isn't correct for module. + layoutTycon denv infoReader ad m false (wordL (tagKeyword "module")) entity + elif entity.IsNamespace then + emptyL + elif entity.IsExceptionDecl then + layoutExnDefn denv entity + else + layoutTycon denv infoReader ad m true WordL.keywordType entity + //-------------------------------------------------------------------------- module private InferredSigPrinting = @@ -1976,6 +1987,8 @@ let stringOfTyparConstraints denv x = x |> PrintTypes.layoutConstraintsWithInfo let layoutTycon denv infoReader ad m (* width *) x = TastDefinitionPrinting.layoutTycon denv infoReader ad m true WordL.keywordType x (* |> Display.squashTo width *) +let layoutEntity denv infoReader ad m x = TastDefinitionPrinting.layoutEntity denv infoReader ad m x + let layoutUnionCases denv x = x |> TastDefinitionPrinting.layoutUnionCaseFields denv true /// Pass negative number as pos in case of single cased discriminated unions diff --git a/src/fsharp/NicePrint.fsi b/src/fsharp/NicePrint.fsi index 59e436b5312..5ddd1455c77 100644 --- a/src/fsharp/NicePrint.fsi +++ b/src/fsharp/NicePrint.fsi @@ -70,6 +70,8 @@ val stringOfTyparConstraints: denv:DisplayEnv -> x:(Typar * TyparConstraint) lis val layoutTycon: denv:DisplayEnv -> infoReader:InfoReader -> ad:AccessorDomain -> m:range -> x:Tycon -> Layout +val layoutEntity: denv:DisplayEnv -> infoReader:InfoReader -> ad:AccessorDomain -> m:range -> x:Entity -> Layout + val layoutUnionCases: denv:DisplayEnv -> x:RecdField list -> Layout val isGeneratedUnionCaseField: pos:int -> f:RecdField -> bool diff --git a/src/fsharp/symbols/Symbols.fs b/src/fsharp/symbols/Symbols.fs index 1146e13a5ac..b9d42526cc4 100644 --- a/src/fsharp/symbols/Symbols.fs +++ b/src/fsharp/symbols/Symbols.fs @@ -24,6 +24,8 @@ open FSharp.Compiler.TypedTree open FSharp.Compiler.TypedTreeBasics open FSharp.Compiler.TcGlobals open FSharp.Compiler.TypedTreeOps +open FSharp.Compiler.Syntax.PrettyNaming +open FSharp.Compiler.AbstractIL type FSharpAccessibility(a:Accessibility, ?isProtected) = let isProtected = defaultArg isProtected false @@ -806,6 +808,108 @@ type FSharpEntity(cenv: SymbolEnv, entity:EntityRef) = member x.TryGetMembersFunctionsAndValues() = try x.MembersFunctionsAndValues with _ -> [||] :> _ + member this.TryGetMetadataText() = + match entity.TryDeref with + | ValueSome entity -> + if entity.IsNamespace then None + else + + let denv = DisplayEnv.Empty cenv.g + let denv = + { denv with + showImperativeTyparAnnotations=true + showHiddenMembers=true + showObsoleteMembers=true + showAttributes=true + shrinkOverloads=false + printVerboseSignatures=false } + + let extraOpenPath = + match entity.CompilationPathOpt with + | Some cpath -> + let rec getOpenPath accessPath acc = + match accessPath with + | [] -> acc + | (name, ModuleOrNamespaceKind.ModuleOrType) :: accessPath -> + getOpenPath accessPath (name :: acc) + | (name, ModuleOrNamespaceKind.Namespace) :: accessPath -> + getOpenPath accessPath (name :: acc) + | (name, ModuleOrNamespaceKind.FSharpModuleWithSuffix) :: accessPath -> + getOpenPath accessPath (name :: acc) + + getOpenPath cpath.AccessPath [] + | _ -> + [] + |> List.rev + + let needOpenType = + match entity.CompilationPathOpt with + | Some cpath -> + match cpath.AccessPath with + | (_, ModuleOrNamespaceKind.ModuleOrType) :: _ -> + match this.DeclaringEntity with + | Some (declaringEntity: FSharpEntity) -> not declaringEntity.IsFSharpModule + | _ -> false + | _ -> false + | _ -> + false + + let denv = + denv.SetOpenPaths + ([ FSharpLib.RootPath + FSharpLib.CorePath + FSharpLib.CollectionsPath + FSharpLib.ControlPath + (IL.splitNamespace FSharpLib.ExtraTopLevelOperatorsName) + extraOpenPath + ]) + + let infoReader = cenv.infoReader + + let openPathL = + extraOpenPath + |> List.map (fun x -> Layout.wordL (TaggedText.tagUnknownEntity x)) + + let pathL = + if List.isEmpty extraOpenPath then + Layout.emptyL + else + Layout.sepListL (Layout.sepL TaggedText.dot) openPathL + + let headerL = + if List.isEmpty extraOpenPath then + Layout.emptyL + else + Layout.(^^) + (Layout.wordL (TaggedText.tagKeyword "namespace")) + pathL + + let openL = + if List.isEmpty openPathL then Layout.emptyL + else + let openKeywordL = + if needOpenType then + Layout.(^^) + (Layout.wordL (TaggedText.tagKeyword "open")) + (Layout.wordL TaggedText.keywordType) + else + Layout.wordL (TaggedText.tagKeyword "open") + Layout.(^^) + openKeywordL + pathL + + Layout.aboveListL + [ + (Layout.(^^) headerL (Layout.sepL TaggedText.lineBreak)) + (Layout.(^^) openL (Layout.sepL TaggedText.lineBreak)) + (NicePrint.layoutEntity denv infoReader AccessibleFromSomewhere range0 entity) + ] + |> LayoutRender.showL + |> SourceText.ofString + |> Some + | _ -> + None + override x.Equals(other: obj) = box x === other || match other with diff --git a/src/fsharp/symbols/Symbols.fsi b/src/fsharp/symbols/Symbols.fsi index a451066d142..ba09617f18b 100644 --- a/src/fsharp/symbols/Symbols.fsi +++ b/src/fsharp/symbols/Symbols.fsi @@ -359,6 +359,9 @@ type FSharpEntity = /// Safe version of `GetMembersFunctionsAndValues`. member TryGetMembersFunctionsAndValues: unit -> IList + /// Get the source text of the entity's signature to be used as metadata. + member TryGetMetadataText: unit -> ISourceText option + /// Represents a delegate signature in an F# symbol [] type FSharpDelegateSignature = diff --git a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs index a577dacb842..f5e05e7cda1 100644 --- a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs +++ b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs @@ -1494,11 +1494,11 @@ FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: FSharp.Compiler.Syntax.Pars FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: FSharp.Compiler.Syntax.ParsedInput get_ParseTree() FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.EditorServices.ParameterLocations] FindParameterLocations(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfExprInYieldOrReturn(FSharp.Compiler.Text.Position) +FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfExpressionBeingDereferencedContainingPos(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfFunctionOrMethodBeingApplied(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfNameOfNearestOuterBindingContainingPos(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfRecordExpressionContainingPos(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfRefCellDereferenceContainingPos(FSharp.Compiler.Text.Position) -FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfExpressionBeingDereferencedContainingPos(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] ValidateBreakpointLocation(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Text.Range]] GetAllArgumentsForFunctionApplicationAtPostion(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[System.Tuple`2[FSharp.Compiler.Syntax.Ident,System.Int32]] TryIdentOfPipelineContainingPosAndNumArgsApplied(FSharp.Compiler.Text.Position) @@ -3821,6 +3821,7 @@ FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[FShar FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Symbols.FSharpEntity] get_DeclaringEntity() FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Symbols.FSharpType] BaseType FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Symbols.FSharpType] get_BaseType() +FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.ISourceText] TryGetMetadataText() FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[System.String] Namespace FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[System.String] TryFullName FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[System.String] TryGetFullCompiledName() diff --git a/vsintegration/src/FSharp.Editor/Common/Constants.fs b/vsintegration/src/FSharp.Editor/Common/Constants.fs index 2c6e7c58fdc..75ad88e3379 100644 --- a/vsintegration/src/FSharp.Editor/Common/Constants.fs +++ b/vsintegration/src/FSharp.Editor/Common/Constants.fs @@ -51,6 +51,10 @@ module internal FSharpConstants = /// "F# Miscellaneous Files" let FSharpMiscellaneousFilesName = "F# Miscellaneous Files" + [] + /// "F# Metadata" + let FSharpMetadataName = "F# Metadata" + [] module internal FSharpProviderConstants = diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 6865bba503f..825a010f387 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -15,6 +15,8 @@ open FSharp.Compiler.EditorServices open FSharp.Compiler.Syntax open FSharp.Compiler.Text +open Microsoft.VisualStudio.FSharp.Editor + type private FSharpGlyph = FSharp.Compiler.EditorServices.FSharpGlyph type private FSharpRoslynGlyph = Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpGlyph @@ -36,6 +38,11 @@ type ProjectId with member this.ToFSharpProjectIdString() = this.Id.ToString("D").ToLowerInvariant() +type Project with + member this.IsFSharpMiscellaneous = this.Name = FSharpConstants.FSharpMiscellaneousFilesName + member this.IsFSharpMetadata = this.Name.StartsWith(FSharpConstants.FSharpMetadataName) + member this.IsFSharpMiscellaneousOrMetadata = this.IsFSharpMiscellaneous || this.IsFSharpMetadata + type Document with member this.TryGetLanguageService<'T when 'T :> ILanguageService>() = match this.Project with @@ -47,6 +54,9 @@ type Document with languageServices.GetService<'T>() |> Some + member this.IsFSharpScript = + isScriptFile this.FilePath + module private SourceText = open System.Runtime.CompilerServices diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs index 47d1443c23e..dd26fbf1026 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs @@ -115,6 +115,9 @@ type internal FSharpDocumentDiagnosticAnalyzer interface IFSharpDocumentDiagnosticAnalyzer with member this.AnalyzeSyntaxAsync(document: Document, cancellationToken: CancellationToken): Task> = + if document.Project.IsFSharpMetadata then Task.FromResult(ImmutableArray.Empty) + else + asyncMaybe { let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) return! @@ -125,14 +128,14 @@ type internal FSharpDocumentDiagnosticAnalyzer |> RoslynHelpers.StartAsyncAsTask cancellationToken member this.AnalyzeSemanticsAsync(document: Document, cancellationToken: CancellationToken): Task> = + if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then Task.FromResult(ImmutableArray.Empty) + else + asyncMaybe { let! parsingOptions, _, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject(document, cancellationToken, userOpName) - if document.Project.Name <> FSharpConstants.FSharpMiscellaneousFilesName || isScriptFile document.FilePath then - return! - FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(checkerProvider.Checker, document, parsingOptions, projectOptions, DiagnosticsType.Semantic) - |> liftAsync - else - return ImmutableArray.Empty + return! + FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(checkerProvider.Checker, document, parsingOptions, projectOptions, DiagnosticsType.Semantic) + |> liftAsync } |> Async.map (Option.defaultValue ImmutableArray.Empty) |> RoslynHelpers.StartAsyncAsTask cancellationToken diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs index 51963b326c8..64007b19de8 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs @@ -34,6 +34,9 @@ type internal SimplifyNameDiagnosticAnalyzer interface IFSharpSimplifyNameDiagnosticAnalyzer with member _.AnalyzeSemanticsAsync(descriptor, document: Document, cancellationToken: CancellationToken) = + if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then Tasks.Task.FromResult(ImmutableArray.Empty) + else + asyncMaybe { do! Option.guard document.FSharpOptions.CodeFixes.SimplifyName do Trace.TraceInformation("{0:n3} (start) SimplifyName", DateTime.Now.TimeOfDay.TotalSeconds) diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs index 4fe92e87665..094796c9e36 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs @@ -24,6 +24,9 @@ type internal UnusedDeclarationsAnalyzer interface IFSharpUnusedDeclarationsDiagnosticAnalyzer with member _.AnalyzeSemanticsAsync(descriptor, document, cancellationToken) = + if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then Threading.Tasks.Task.FromResult(ImmutableArray.Empty) + else + asyncMaybe { do! Option.guard document.FSharpOptions.CodeFixes.UnusedDeclarations diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs index b197558ce86..c63341ecd0a 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs @@ -38,6 +38,9 @@ type internal UnusedOpensDiagnosticAnalyzer interface IFSharpUnusedOpensDiagnosticAnalyzer with member _.AnalyzeSemanticsAsync(descriptor, document: Document, cancellationToken: CancellationToken) = + if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then Tasks.Task.FromResult(ImmutableArray.Empty) + else + asyncMaybe { do Trace.TraceInformation("{0:n3} (start) UnusedOpensAnalyzer", DateTime.Now.TimeOfDay.TotalSeconds) let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index 74f0e506c76..ff9a06b78d6 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -28,8 +28,8 @@ - + diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs index 317448f5cb8..5413dffc851 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs @@ -12,6 +12,7 @@ open Microsoft.CodeAnalysis open Microsoft.VisualStudio open Microsoft.VisualStudio.FSharp.Editor open Microsoft.VisualStudio.LanguageServices +open Microsoft.VisualStudio.LanguageServices.ProjectSystem open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics #nowarn "9" // NativePtr.toNativeInt @@ -23,10 +24,11 @@ type internal FSharpCheckerProvider ( analyzerService: IFSharpDiagnosticAnalyzerService, [)>] workspace: VisualStudioWorkspace, + projectContextFactory: IWorkspaceProjectContextFactory, settings: EditorOptions ) = - let metadataAsSource = FSharpMetadataAsSourceService() + let metadataAsSource = FSharpMetadataAsSourceService(projectContextFactory) let tryGetMetadataSnapshot (path, timeStamp) = try diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs index 08e62c70a2b..88532d1485b 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs @@ -259,16 +259,22 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor // For now, disallow miscellaneous workspace since we are using the hacky F# miscellaneous files project. if document.Project.Solution.Workspace.Kind = WorkspaceKind.MiscellaneousFiles then reply.Reply None - elif document.Project.Name = FSharpConstants.FSharpMiscellaneousFilesName then + elif document.Project.IsFSharpMiscellaneousOrMetadata then let! options = tryComputeOptionsByFile document ct userOpName - reply.Reply options + if ct.IsCancellationRequested then + reply.Reply None + else + reply.Reply options else // We only care about the latest project in the workspace's solution. // We do this to prevent any possible cache thrashing in FCS. let project = document.Project.Solution.Workspace.CurrentSolution.GetProject(document.Project.Id) if not (isNull project) then let! options = tryComputeOptions project ct - reply.Reply options + if ct.IsCancellationRequested then + reply.Reply None + else + reply.Reply options else reply.Reply None with @@ -280,7 +286,7 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor reply.Reply None else try - if project.Solution.Workspace.Kind = WorkspaceKind.MiscellaneousFiles || project.Name = FSharpConstants.FSharpMiscellaneousFilesName then + if project.Solution.Workspace.Kind = WorkspaceKind.MiscellaneousFiles || project.IsFSharpMiscellaneousOrMetadata then reply.Reply None else // We only care about the latest project in the workspace's solution. @@ -288,7 +294,10 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor let project = project.Solution.Workspace.CurrentSolution.GetProject(project.Id) if not (isNull project) then let! options = tryComputeOptions project ct - reply.Reply options + if ct.IsCancellationRequested then + reply.Reply None + else + reply.Reply options else reply.Reply None with @@ -296,10 +305,18 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor reply.Reply None | FSharpProjectOptionsMessage.ClearOptions(projectId) -> - cache.TryRemove(projectId) |> ignore + match cache.TryRemove(projectId) with + | true, (_, _, projectOptions) -> + checkerProvider.Checker.ClearCache([projectOptions]) + | _ -> + () legacyProjectSites.TryRemove(projectId) |> ignore | FSharpProjectOptionsMessage.ClearSingleFileOptionsCache(documentId) -> - singleFileCache.TryRemove(documentId) |> ignore + match singleFileCache.TryRemove(documentId) with + | true, (_, _, projectOptions) -> + checkerProvider.Checker.ClearCache([projectOptions]) + | _ -> + () } let agent = MailboxProcessor.Start((fun agent -> loop agent), cancellationToken = cancellationTokenSource.Token) @@ -362,6 +379,13 @@ type internal FSharpProjectOptionsManager | _ -> () ) + workspace.DocumentClosed.Add(fun args -> + let doc = args.Document + let proj = doc.Project + if proj.Language = LanguageNames.FSharp && proj.IsFSharpMiscellaneousOrMetadata then + reactor.ClearSingleFileOptionsCache(doc.Id) + ) + member _.SetLegacyProjectSite (projectId, projectSite) = reactor.SetLegacyProjectSite (projectId, projectSite) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index 7ba63e977de..a4c4afbebc0 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -61,6 +61,7 @@ type private FSharpSolutionEvents(projectManager: FSharpProjectOptionsManager) = member _.OnAfterCloseSolution(_) = projectManager.Checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() + projectManager.MetadataAsSource.ClearGeneratedFiles() VSConstants.S_OK member _.OnAfterLoadProject(_, _) = VSConstants.E_NOTIMPL diff --git a/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs index 9913d6f0d2c..57abc41755c 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs @@ -18,6 +18,7 @@ open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Navigation open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation open Microsoft.VisualStudio.ComponentModelHost +open Microsoft.VisualStudio.LanguageServices.ProjectSystem open Microsoft.VisualStudio open Microsoft.VisualStudio.Editor @@ -26,42 +27,16 @@ open Microsoft.VisualStudio.Shell open Microsoft.VisualStudio.Shell.Interop open Microsoft.VisualStudio.TextManager.Interop -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 generateTemporaryDocument (asmIdentity: AssemblyIdentity, name: string, metadataReferences) = let rootPath = Path.Combine(Path.GetTempPath(), "MetadataAsSource") - let extension = ".cs" + let extension = ".fsi" 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( @@ -74,36 +49,14 @@ module internal MetadataAsSource = ProjectInfo.Create( projectId, VersionStamp.Default, - name = asmIdentity.Name, + name = FSharpConstants.FSharpMetadataName + " - " + asmIdentity.Name, assemblyName = asmIdentity.Name, - language = LanguageNames.CSharp, - compilationOptions = compilationOptions, - parseOptions = parseOptions, - documents = [|assemblyInfoDocument;documentInfo|], + language = LanguageNames.FSharp, + documents = [|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) @@ -119,6 +72,7 @@ module internal MetadataAsSource = let textBuffer = editorAdaptersFactory.GetDataBuffer(vsTextBuffer) if not fileAlreadyOpen then + ErrorHandler.ThrowOnFailure(vsTextBuffer.SetStateFlags(uint32 BUFFERSTATEFLAGS.BSF_USER_READONLY)) |> ignore 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 @@ -137,19 +91,55 @@ module internal MetadataAsSource = 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 +type internal FSharpMetadataAsSourceService(projectContextFactory: IWorkspaceProjectContextFactory) = + + let serviceProvider = ServiceProvider.GlobalProvider + let projs = System.Collections.Concurrent.ConcurrentDictionary() + + let createMetadataProjectContext (projInfo: ProjectInfo) (docInfo: DocumentInfo) = + let projectContext = projectContextFactory.CreateProjectContext(LanguageNames.FSharp, 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 + + let clear filePath (projectContext: IWorkspaceProjectContext) = + projs.TryRemove(filePath) |> ignore + projectContext.Dispose() + try + File.Delete filePath |> ignore + with + | _ -> () + + member _.ClearGeneratedFiles() = + let projsArr = projs.ToArray() + projsArr + |> Array.iter (fun pair -> + clear pair.Key pair.Value + ) + + member _.ShowDocument(projInfo: ProjectInfo, filePath: string, text: Text.SourceText) = + match projInfo.Documents |> Seq.tryFind (fun doc -> doc.FilePath = filePath) with + | Some document -> + let _ = + let directoryName = Path.GetDirectoryName(filePath) + if Directory.Exists(directoryName) |> not then + Directory.CreateDirectory(directoryName) |> ignore + use fileStream = new FileStream(filePath, IO.FileMode.Create) + use writer = new StreamWriter(fileStream) + text.Write(writer) + + let projectContext = createMetadataProjectContext projInfo document + + projs.[filePath] <- projectContext + + MetadataAsSource.showDocument(filePath, Path.GetFileName(filePath), serviceProvider) + | _ -> + None \ 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 0e565801fd3..91106217e99 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs @@ -33,38 +33,18 @@ 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 -> let document = args.Document - if document.Project.Language = FSharpConstants.FSharpLanguageName && document.Project.Name <> FSharpConstants.FSharpMiscellaneousFilesName then + if document.Project.Language = FSharpConstants.FSharpLanguageName && + not document.Project.IsFSharpMiscellaneousOrMetadata then match files.TryRemove(document.FilePath) with | true, projectContext -> optionsManager.ClearSingleFileOptionsCache(document.Id) @@ -74,13 +54,13 @@ type internal SingleFileWorkspaceMap(workspace: VisualStudioWorkspace, workspace.DocumentClosed.Add(fun args -> let document = args.Document - match files.TryRemove(document.FilePath) with - | true, projectContext -> - optionsManager.ClearSingleFileOptionsCache(document.Id) - projectContext.Dispose() - | _ -> () - - optionsManager.MetadataAsSource.CSharpFiles.TryRemove(document.FilePath) |> ignore + if document.Project.Language = FSharpConstants.FSharpLanguageName && + document.Project.IsFSharpMiscellaneousOrMetadata then + match files.TryRemove(document.FilePath) with + | true, projectContext -> + optionsManager.ClearSingleFileOptionsCache(document.Id) + projectContext.Dispose() + | _ -> () ) do diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index df85f013fc8..5be4f843ef8 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -156,7 +156,7 @@ type internal FSharpGoToDefinitionNavigableItem(document, sourceSpan) = [] type internal FSharpGoToDefinitionResult = | NavigableItem of FSharpNavigableItem - | ExternalAssembly of ProjectInfo * DocumentInfo * FSharpSymbolUse * FindDeclExternalSymbol + | ExternalAssembly of FSharpSymbolUse * MetadataReference seq type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpProjectOptionsManager) = let userOpName = "GoToDefinition" @@ -272,63 +272,68 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP 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) + let metadataReferences = originDocument.Project.MetadataReferences + return (FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences), idRange) | FindDeclResult.DeclFound targetRange -> - // if goto definition is called at we are alread at the declaration location of a symbol in - // either a signature or an implementation file then we jump to it's respective postion in thethe - if lexerSymbol.Range = targetRange then - // jump from signature to the corresponding implementation - if isSignatureFile originDocument.FilePath then - let implFilePath = Path.ChangeExtension (originDocument.FilePath,"fs") - if not (File.Exists implFilePath) then return! None else - let! implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath + // If the file does not actually exist, it's external considered external. + if not (File.Exists targetRange.FileName) then + let metadataReferences = originDocument.Project.MetadataReferences + return (FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences), idRange) + else + // if goto definition is called at we are alread at the declaration location of a symbol in + // either a signature or an implementation file then we jump to it's respective postion in thethe + if lexerSymbol.Range = targetRange then + // jump from signature to the corresponding implementation + if isSignatureFile originDocument.FilePath then + let implFilePath = Path.ChangeExtension (originDocument.FilePath,"fs") + if not (File.Exists implFilePath) then return! None else + let! implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath - let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument, projectOptions) - let! implSourceText = implDocument.GetTextAsync() |> liftTaskAsync - let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange) - let navItem = FSharpGoToDefinitionNavigableItem (implDocument, implTextSpan) - 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 - | FindDeclResult.DeclFound targetRange -> - let! sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName - let! sigSourceText = sigDocument.GetTextAsync () |> liftTaskAsync - let! sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sigSourceText, targetRange) + let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument, projectOptions) + let! implSourceText = implDocument.GetTextAsync() |> liftTaskAsync + let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange) + let navItem = FSharpGoToDefinitionNavigableItem (implDocument, implTextSpan) + 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 + | FindDeclResult.DeclFound targetRange -> + let! sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName + let! sigSourceText = sigDocument.GetTextAsync () |> liftTaskAsync + let! sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sigSourceText, targetRange) + let navItem = FSharpGoToDefinitionNavigableItem (sigDocument, sigTextSpan) + return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) + | _ -> + return! None + // when the target range is different follow the navigation convention of + // - gotoDefn origin = signature , gotoDefn destination = signature + // - gotoDefn origin = implementation, gotoDefn destination = implementation + else + let! sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName + let! sigSourceText = sigDocument.GetTextAsync () |> liftTaskAsync + let! sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sigSourceText, targetRange) + // 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 (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) - | _ -> - return! None - // when the target range is different follow the navigation convention of - // - gotoDefn origin = signature , gotoDefn destination = signature - // - gotoDefn origin = implementation, gotoDefn destination = implementation - else - let! sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName - let! sigSourceText = sigDocument.GetTextAsync () |> liftTaskAsync - let! sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sigSourceText, targetRange) - // 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 (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 = - // Bugfix: apparently sigDocument not always is a signature file - if isSignatureFile sigDocument.FilePath then Path.ChangeExtension (sigDocument.FilePath, "fs") - else sigDocument.FilePath - - let! implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath - - let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(implDocument, CancellationToken.None, userOpName) + 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 = + // Bugfix: apparently sigDocument not always is a signature file + if isSignatureFile sigDocument.FilePath then Path.ChangeExtension (sigDocument.FilePath, "fs") + else sigDocument.FilePath + + let! implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath + + let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(implDocument, CancellationToken.None, userOpName) - let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument, projectOptions) + let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument, projectOptions) - let! implSourceText = implDocument.GetTextAsync () |> liftTaskAsync - let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange) - let navItem = FSharpGoToDefinitionNavigableItem (implDocument, implTextSpan) - return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) + let! implSourceText = implDocument.GetTextAsync () |> liftTaskAsync + let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange) + let navItem = FSharpGoToDefinitionNavigableItem (implDocument, implTextSpan) + return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) | _ -> return! None } diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs index 49f2d076174..8af615f8b07 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs @@ -21,6 +21,7 @@ open Microsoft.VisualStudio.LanguageServices open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.EditorServices +open FSharp.Compiler.Symbols [)>] [)>] @@ -69,30 +70,91 @@ type internal FSharpGoToDefinitionService 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 - | FindDeclExternalSymbol.Constructor(tyName, _) - | FindDeclExternalSymbol.Event(tyName, _) - | FindDeclExternalSymbol.Field(tyName, _) - | FindDeclExternalSymbol.Method(tyName, _, _, _) - | FindDeclExternalSymbol.Property(tyName, _) - | FindDeclExternalSymbol.Type(tyName) -> tyName - - 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 + | FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences) -> + let textOpt = + match targetSymbolUse.Symbol with + | :? FSharpEntity as symbol -> + symbol.TryGetMetadataText() + |> Option.map (fun text -> text, symbol.DisplayName) + | :? FSharpMemberOrFunctionOrValue as symbol -> + symbol.ApparentEnclosingEntity.TryGetMetadataText() + |> Option.map (fun text -> text, symbol.ApparentEnclosingEntity.DisplayName) + | _ -> + None + + match textOpt with + | Some (text, fileName) -> + let tmpProjInfo, tmpDocInfo = + MetadataAsSource.generateTemporaryDocument( + AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName), + fileName, + metadataReferences) + let tmpShownDocOpt = metadataAsSourceService.ShowDocument(tmpProjInfo, tmpDocInfo.FilePath, SourceText.From(text.ToString())) + match tmpShownDocOpt with + | Some tmpShownDoc -> + let goToAsync = + asyncMaybe { + let userOpName = "TryGoToDefinition" + let! _, _, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject (tmpShownDoc, cancellationToken, userOpName) + let! _, _, checkResults = checkerProvider.Checker.ParseAndCheckDocument(tmpShownDoc, projectOptions, userOpName) + let! r = + let rec areTypesEqual (ty1: FSharpType) (ty2: FSharpType) = + let ty1 = ty1.StripAbbreviations() + let ty2 = ty2.StripAbbreviations() + ty1.TypeDefinition.DisplayName = ty2.TypeDefinition.DisplayName && + ty1.TypeDefinition.AccessPath = ty2.TypeDefinition.AccessPath && + ty1.GenericArguments.Count = ty2.GenericArguments.Count && + ( + (ty1.GenericArguments, ty2.GenericArguments) + ||> Seq.forall2 areTypesEqual + ) + + // This tries to find the best possible location of the target symbol's location in the metadata source. + // We really should rely on symbol equality within FCS instead of doing it here, + // but the generated metadata as source isn't perfect for symbol equality. + checkResults.GetAllUsesOfAllSymbolsInFile(cancellationToken) + |> Seq.tryFind (fun x -> + match x.Symbol, targetSymbolUse.Symbol with + | (:? FSharpEntity as symbol1), (:? FSharpEntity as symbol2) when x.IsFromDefinition -> + symbol1.DisplayName = symbol2.DisplayName + | (:? FSharpMemberOrFunctionOrValue as symbol1), (:? FSharpMemberOrFunctionOrValue as symbol2) -> + symbol1.DisplayName = symbol2.DisplayName && + symbol1.GenericParameters.Count = symbol2.GenericParameters.Count && + symbol1.CurriedParameterGroups.Count = symbol2.CurriedParameterGroups.Count && + ( + (symbol1.CurriedParameterGroups, symbol2.CurriedParameterGroups) + ||> Seq.forall2 (fun pg1 pg2 -> + pg1.Count = pg2.Count && + ( + (pg1, pg2) + ||> Seq.forall2 (fun p1 p2 -> + areTypesEqual p1.Type p2.Type + ) + ) + ) + ) && + areTypesEqual symbol1.ReturnParameter.Type symbol2.ReturnParameter.Type + | _ -> + false + ) + |> Option.map (fun x -> x.Range) + + let span = + match RoslynHelpers.TryFSharpRangeToTextSpan(tmpShownDoc.GetTextAsync(cancellationToken).Result, r) with + | Some span -> span + | _ -> TextSpan() + + return span + } + + let span = + match Async.RunSynchronously(goToAsync, cancellationToken = cancellationToken) with + | Some span -> span + | _ -> TextSpan() + + let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, span) + gtd.NavigateToItem(navItem, statusBar) + true | _ -> statusBar.TempMessage (SR.CannotDetermineSymbol()) false diff --git a/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.QuickInfo.fs b/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.QuickInfo.fs index 9ba65877a45..72d7feae3d6 100644 --- a/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.QuickInfo.fs +++ b/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.QuickInfo.fs @@ -352,6 +352,7 @@ Full name: Microsoft.FSharp.Control.Async""".TrimStart().Replace("\r\n", "\n") [] [] [] + [] //This is to test when the message is null in the TypeProviderXmlDocAttribute for TypeProvider Type member public this.``TypeProvider.XmlDocAttribute.Type.WithNullComment``() = @@ -359,12 +360,13 @@ Full name: Microsoft.FSharp.Control.Async""".TrimStart().Replace("\r\n", "\n") let a = typeof """ this.AssertQuickInfoContainsAtStartOfMarker (fileContents, "T(*Marker*)", - "type T =\n new : unit -> T\n static member M : unit -> int []\n static member StaticProp : decimal\n event Event1 : EventHandler", + "type T =\n new : unit -> T\n static member M : unit -> int []\n static member StaticProp : decimal\n member Event1 : EventHandler", addtlRefAssy = [PathRelativeToTestAssembly( @"XmlDocAttributeWithNullComment.dll")]) [] [] [] + [] //This is to test when there is empty message from the TypeProviderXmlDocAttribute for TypeProvider Type member public this.``TypeProvider.XmlDocAttribute.Type.WithEmptyComment``() = @@ -372,7 +374,7 @@ Full name: Microsoft.FSharp.Control.Async""".TrimStart().Replace("\r\n", "\n") let a = typeof """ this.AssertQuickInfoContainsAtStartOfMarker (fileContents, "T(*Marker*)", - "type T =\n new : unit -> T\n static member M : unit -> int []\n static member StaticProp : decimal\n event Event1 : EventHandler", + "type T =\n new : unit -> T\n static member M : unit -> int []\n static member StaticProp : decimal\n member Event1 : EventHandler", addtlRefAssy = [PathRelativeToTestAssembly( @"XmlDocAttributeWithEmptyComment.dll")]) @@ -509,6 +511,7 @@ Full name: Microsoft.FSharp.Control.Async""".TrimStart().Replace("\r\n", "\n") [] [] [] + [] //This is to test when the message is null in the TypeProviderXmlDocAttribute for TypeProvider Event member public this.``TypeProvider.XmlDocAttribute.Event.WithNullComment``() = @@ -517,12 +520,13 @@ Full name: Microsoft.FSharp.Control.Async""".TrimStart().Replace("\r\n", "\n") t.Event1(*Marker*)""" this.AssertQuickInfoContainsAtStartOfMarker (fileContents, "Event1(*Marker*)", - "event N.T.Event1: IEvent", + "member N.T.Event1: IEvent", addtlRefAssy = [PathRelativeToTestAssembly( @"XmlDocAttributeWithNullComment.dll")]) [] [] [] + [] //This is to test when there is empty message from the TypeProviderXmlDocAttribute for TypeProvider Event member public this.``TypeProvider.XmlDocAttribute.Event.WithEmptyComment``() = @@ -531,7 +535,7 @@ Full name: Microsoft.FSharp.Control.Async""".TrimStart().Replace("\r\n", "\n") t.Event1(*Marker*)""" this.AssertQuickInfoContainsAtStartOfMarker (fileContents, "Event1(*Marker*)", - "event N.T.Event1: IEvent", + "member N.T.Event1: IEvent", addtlRefAssy = [PathRelativeToTestAssembly( @"XmlDocAttributeWithEmptyComment.dll")])