From 9e3f1607fe8c7215f1749f9f15be542afe86372a Mon Sep 17 00:00:00 2001 From: Will Smith Date: Mon, 29 Mar 2021 19:32:10 -0700 Subject: [PATCH 1/9] Initial work for generating a signature of a given symbol for viewing metadata --- src/fsharp/symbols/Symbols.fs | 31 ++++++++++ src/fsharp/symbols/Symbols.fsi | 3 + .../LanguageService/MetadataAsSource.fs | 62 +++---------------- .../LanguageService/SingleFileWorkspaceMap.fs | 12 ++-- .../Navigation/GoToDefinition.fs | 2 +- .../Navigation/GoToDefinitionService.fs | 40 +++++------- 6 files changed, 65 insertions(+), 85 deletions(-) diff --git a/src/fsharp/symbols/Symbols.fs b/src/fsharp/symbols/Symbols.fs index 1146e13a5ac..c6486e784b6 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,35 @@ type FSharpEntity(cenv: SymbolEnv, entity:EntityRef) = member x.TryGetMembersFunctionsAndValues() = try x.MembersFunctionsAndValues with _ -> [||] :> _ + member _.TryGenerateSignatureText() = + match entity.TryDeref with + | ValueSome entity -> + let denv = DisplayEnv.Empty cenv.g + let denv = + { denv with + showImperativeTyparAnnotations=true + showHiddenMembers=true + showObsoleteMembers=true + showAttributes=true + shrinkOverloads=false + printVerboseSignatures=false } + + let denv = + denv.SetOpenPaths + ([ FSharpLib.RootPath + FSharpLib.CorePath + FSharpLib.CollectionsPath + FSharpLib.ControlPath + (IL.splitNamespace FSharpLib.ExtraTopLevelOperatorsName) + ]) + + let infoReader = cenv.infoReader + NicePrint.layoutTycon 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..ffb55472318 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 + /// Generates the signature of the symbol as source text. + member TryGenerateSignatureText: unit -> ISourceText option + /// Represents a delegate signature in an F# symbol [] type FSharpDelegateSignature = diff --git a/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs index 9913d6f0d2c..da8a0df9b2d 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs @@ -26,42 +26,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( @@ -76,34 +50,12 @@ module internal MetadataAsSource = VersionStamp.Default, name = 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) @@ -139,9 +91,9 @@ module internal MetadataAsSource = [] type internal FSharpMetadataAsSourceService() = - member val CSharpFiles = System.Collections.Concurrent.ConcurrentDictionary(StringComparer.OrdinalIgnoreCase) + member val Files = System.Collections.Concurrent.ConcurrentDictionary(StringComparer.OrdinalIgnoreCase) - member this.ShowCSharpDocument(projInfo: ProjectInfo, docInfo: DocumentInfo, text: Text.SourceText) = + member this.ShowDocument(projInfo: ProjectInfo, docInfo: DocumentInfo, text: Text.SourceText) = let _ = let directoryName = Path.GetDirectoryName(docInfo.FilePath) if Directory.Exists(directoryName) |> not then @@ -150,6 +102,6 @@ type internal FSharpMetadataAsSourceService() = use writer = new StreamWriter(fileStream) text.Write(writer) - this.CSharpFiles.[docInfo.FilePath] <- (projInfo, docInfo) + this.Files.[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 0e565801fd3..c7eec6c61b5 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs @@ -33,8 +33,8 @@ 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) + 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) @@ -54,10 +54,10 @@ type internal SingleFileWorkspaceMap(workspace: VisualStudioWorkspace, 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 + if optionsManager.MetadataAsSource.Files.ContainsKey(document.FilePath) && workspace.CurrentSolution.GetDocumentIdsWithFilePath(document.FilePath).Length = 0 then + match optionsManager.MetadataAsSource.Files.TryGetValue(document.FilePath) with | true, (projInfo, docInfo) -> - files.[document.FilePath] <- createCSharpMetadataProjectContext projInfo docInfo + files.[document.FilePath] <- createMetadataProjectContext projInfo docInfo | _ -> () ) @@ -80,7 +80,7 @@ type internal SingleFileWorkspaceMap(workspace: VisualStudioWorkspace, projectContext.Dispose() | _ -> () - optionsManager.MetadataAsSource.CSharpFiles.TryRemove(document.FilePath) |> ignore + optionsManager.MetadataAsSource.Files.TryRemove(document.FilePath) |> ignore ) do diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index df85f013fc8..b2a66be62d4 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -272,7 +272,7 @@ 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) + let tmpProjInfo, tmpDocId = MetadataAsSource.generateTemporaryDocument(AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName), targetSymbolUse.Symbol.DisplayName, originDocument.Project.MetadataReferences) return (FSharpGoToDefinitionResult.ExternalAssembly(tmpProjInfo, tmpDocId, targetSymbolUse, targetExternalSym), idRange) | FindDeclResult.DeclFound targetRange -> diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs index 49f2d076174..c1f05d490de 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs @@ -69,30 +69,24 @@ 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 + | FSharpGoToDefinitionResult.ExternalAssembly(tmpProjInfo, tmpDocInfo, targetSymbolUse, _targetExternalSymbol) -> + let textOpt = + match targetSymbolUse.Symbol with + | :? FSharp.Compiler.Symbols.FSharpEntity as symbol -> + symbol.TryGenerateSignatureText() + | :? FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue as symbol -> + symbol.ApparentEnclosingEntity.TryGenerateSignatureText() + | _ -> + None - 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 + match textOpt with + | Some text -> + let tmpShownDocOpt = metadataAsSourceService.ShowDocument(tmpProjInfo, tmpDocInfo, SourceText.From(text.ToString())) + match tmpShownDocOpt with + | Some tmpShownDoc -> + let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, TextSpan()) + gtd.NavigateToItem(navItem, statusBar) + true | _ -> statusBar.TempMessage (SR.CannotDetermineSymbol()) false From 224a5b6473232d20ab6c4bd0953d40337b722d61 Mon Sep 17 00:00:00 2001 From: Will Smith Date: Mon, 29 Mar 2021 19:44:10 -0700 Subject: [PATCH 2/9] Do not output the 'event' keyword --- src/fsharp/NicePrint.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fsharp/NicePrint.fs b/src/fsharp/NicePrint.fs index a91660a6bf4..39dc0e86444 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 From 3a7729b248f56e1f726600e5323072ebd361ed32 Mon Sep 17 00:00:00 2001 From: Will Smith Date: Tue, 30 Mar 2021 12:38:51 -0700 Subject: [PATCH 3/9] Adding open paths and fixing surface area --- src/fsharp/symbols/Symbols.fs | 59 ++++++++++++++++++- .../SurfaceArea.netstandard.fs | 3 +- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/fsharp/symbols/Symbols.fs b/src/fsharp/symbols/Symbols.fs index c6486e784b6..cfb813e74e1 100644 --- a/src/fsharp/symbols/Symbols.fs +++ b/src/fsharp/symbols/Symbols.fs @@ -808,7 +808,7 @@ type FSharpEntity(cenv: SymbolEnv, entity:EntityRef) = member x.TryGetMembersFunctionsAndValues() = try x.MembersFunctionsAndValues with _ -> [||] :> _ - member _.TryGenerateSignatureText() = + member this.TryGenerateSignatureText() = match entity.TryDeref with | ValueSome entity -> let denv = DisplayEnv.Empty cenv.g @@ -821,17 +821,70 @@ type FSharpEntity(cenv: SymbolEnv, entity:EntityRef) = 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 [] + | _ -> + [] + + 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) + (IL.splitNamespace FSharpLib.ExtraTopLevelOperatorsName) + extraOpenPath ]) let infoReader = cenv.infoReader - NicePrint.layoutTycon denv infoReader AccessibleFromSomewhere range0 entity |> LayoutRender.showL + + let openPathL = + extraOpenPath + |> List.map (fun x -> Layout.wordL (TaggedText.tagUnknownEntity x)) + + let openL = + if List.isEmpty openPathL then Layout.emptyL + else + let openKeywordL = + if needOpenType then + Layout.(^^) + (Layout.wordL (FSharp.Compiler.Text.TaggedText.tagKeyword "open")) + (Layout.wordL FSharp.Compiler.Text.TaggedText.keywordType) + else + Layout.wordL (FSharp.Compiler.Text.TaggedText.tagKeyword "open") + + Layout.(^^) + openKeywordL + (Layout.sepListL (Layout.sepL TaggedText.dot) openPathL) + + Layout.aboveL + (Layout.(^^) openL (Layout.sepL TaggedText.lineBreak)) + (NicePrint.layoutTycon denv infoReader AccessibleFromSomewhere range0 entity) + |> LayoutRender.showL |> SourceText.ofString |> Some | _ -> diff --git a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs index a577dacb842..c9ca1cfdbc4 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] TryGenerateSignatureText() 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() From 23dde30fe9a06808d90b0a2b081b2c238bc88c54 Mon Sep 17 00:00:00 2001 From: Will Smith Date: Tue, 30 Mar 2021 15:11:27 -0700 Subject: [PATCH 4/9] Fixing tests. Adding more extensions to Project and Document. Better handling of determining if we should do semantic diagnostics for a given file. --- .../src/FSharp.Editor/Common/Constants.fs | 4 ++ .../src/FSharp.Editor/Common/Extensions.fs | 10 +++ .../Diagnostics/DocumentDiagnosticAnalyzer.fs | 12 ++-- .../SimplifyNameDiagnosticAnalyzer.fs | 3 + .../Diagnostics/UnusedDeclarationsAnalyzer.fs | 3 + .../UnusedOpensDiagnosticAnalyzer.fs | 3 + .../src/FSharp.Editor/FSharp.Editor.fsproj | 2 +- .../LanguageService/FSharpCheckerProvider.fs | 4 +- .../FSharpProjectOptionsManager.fs | 4 +- .../LanguageService/MetadataAsSource.fs | 66 ++++++++++++++----- .../LanguageService/SingleFileWorkspaceMap.fs | 38 +++-------- .../Navigation/GoToDefinitionService.fs | 2 +- .../Tests.LanguageService.QuickInfo.fs | 8 +-- 13 files changed, 98 insertions(+), 61 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Common/Constants.fs b/vsintegration/src/FSharp.Editor/Common/Constants.fs index 2c6e7c58fdc..55f7825f22e 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 Files" + let FSharpMetadataFilesName = "F# Metadata Files" + [] module internal FSharpProviderConstants = diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 6865bba503f..a2dcff17191 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.IsFSharpMiscellaneousOrMetadata = + this.Name <> FSharpConstants.FSharpMiscellaneousFilesName || + this.Name <> FSharpConstants.FSharpMetadataFilesName + 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..57fca069cba 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs @@ -125,14 +125,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..39955048a98 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(workspace, 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..509294f60ef 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs @@ -259,7 +259,7 @@ 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 else @@ -280,7 +280,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. diff --git a/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs index da8a0df9b2d..04bf1d894b7 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 @@ -48,7 +49,7 @@ module internal MetadataAsSource = ProjectInfo.Create( projectId, VersionStamp.Default, - name = asmIdentity.Name, + name = FSharpConstants.FSharpMetadataFilesName, assemblyName = asmIdentity.Name, language = LanguageNames.FSharp, documents = [|documentInfo|], @@ -71,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 @@ -89,19 +91,49 @@ module internal MetadataAsSource = None [] -type internal FSharpMetadataAsSourceService() = - - member val Files = System.Collections.Concurrent.ConcurrentDictionary(StringComparer.OrdinalIgnoreCase) - - member this.ShowDocument(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.Files.[docInfo.FilePath] <- (projInfo, docInfo) - - MetadataAsSource.showDocument(docInfo.FilePath, docInfo.Name, ServiceProvider.GlobalProvider) \ No newline at end of file +type internal FSharpMetadataAsSourceService(workspace: Workspace, projectContextFactory: IWorkspaceProjectContextFactory) = + + 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 <- FSharpConstants.FSharpMetadataFilesName + 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 + workspace.DocumentClosed.Add(fun args -> + let doc = args.Document + let proj = doc.Project + if proj.Language = LanguageNames.FSharp then + match projs.TryGetValue(doc.FilePath) with + | true, projectContext -> projectContext.Dispose() + | _ -> () + ) + + 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) + " [Metadata]", ServiceProvider.GlobalProvider) + | _ -> + 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 c7eec6c61b5..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 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 - 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.Files.ContainsKey(document.FilePath) && workspace.CurrentSolution.GetDocumentIdsWithFilePath(document.FilePath).Length = 0 then - match optionsManager.MetadataAsSource.Files.TryGetValue(document.FilePath) with - | true, (projInfo, docInfo) -> - files.[document.FilePath] <- createMetadataProjectContext 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.Files.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/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs index c1f05d490de..160e68118ab 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs @@ -81,7 +81,7 @@ type internal FSharpGoToDefinitionService match textOpt with | Some text -> - let tmpShownDocOpt = metadataAsSourceService.ShowDocument(tmpProjInfo, tmpDocInfo, SourceText.From(text.ToString())) + let tmpShownDocOpt = metadataAsSourceService.ShowDocument(tmpProjInfo, tmpDocInfo.FilePath, SourceText.From(text.ToString())) match tmpShownDocOpt with | Some tmpShownDoc -> let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, TextSpan()) diff --git a/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.QuickInfo.fs b/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.QuickInfo.fs index 9ba65877a45..540317a3e44 100644 --- a/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.QuickInfo.fs +++ b/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.QuickInfo.fs @@ -359,7 +359,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( @"XmlDocAttributeWithNullComment.dll")]) [] @@ -372,7 +372,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")]) @@ -517,7 +517,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( @"XmlDocAttributeWithNullComment.dll")]) [] @@ -531,7 +531,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")]) From 811b258b66d3ebdfd7a5d710734d019df1f9e9f9 Mon Sep 17 00:00:00 2001 From: Will Smith Date: Tue, 30 Mar 2021 19:11:56 -0700 Subject: [PATCH 5/9] Trying to go to definition on metadata. Managing metadata as source projects. --- .../src/FSharp.Editor/Common/Constants.fs | 4 +-- .../src/FSharp.Editor/Common/Extensions.fs | 5 +-- .../LanguageService/FSharpCheckerProvider.fs | 2 +- .../FSharpProjectOptionsManager.fs | 34 ++++++++++++++++--- .../LanguageService/LanguageService.fs | 1 + .../LanguageService/MetadataAsSource.fs | 30 +++++++++------- .../Navigation/GoToDefinition.fs | 3 +- .../Navigation/GoToDefinitionService.fs | 27 +++++++++++++-- 8 files changed, 80 insertions(+), 26 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Common/Constants.fs b/vsintegration/src/FSharp.Editor/Common/Constants.fs index 55f7825f22e..75ad88e3379 100644 --- a/vsintegration/src/FSharp.Editor/Common/Constants.fs +++ b/vsintegration/src/FSharp.Editor/Common/Constants.fs @@ -52,8 +52,8 @@ module internal FSharpConstants = let FSharpMiscellaneousFilesName = "F# Miscellaneous Files" [] - /// "F# Metadata Files" - let FSharpMetadataFilesName = "F# Metadata 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 a2dcff17191..0adb75753bf 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -39,9 +39,10 @@ type ProjectId with this.Id.ToString("D").ToLowerInvariant() type Project with + member this.IsFSharpMiscellaneous = this.Name = FSharpConstants.FSharpMiscellaneousFilesName member this.IsFSharpMiscellaneousOrMetadata = - this.Name <> FSharpConstants.FSharpMiscellaneousFilesName || - this.Name <> FSharpConstants.FSharpMetadataFilesName + this.IsFSharpMiscellaneous || + this.Name.StartsWith(FSharpConstants.FSharpMetadataName) type Document with member this.TryGetLanguageService<'T when 'T :> ILanguageService>() = diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs index 39955048a98..5413dffc851 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs @@ -28,7 +28,7 @@ type internal FSharpCheckerProvider settings: EditorOptions ) = - let metadataAsSource = FSharpMetadataAsSourceService(workspace, projectContextFactory) + 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 509294f60ef..88532d1485b 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs @@ -261,14 +261,20 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor reply.Reply None 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 @@ -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 04bf1d894b7..57abc41755c 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs @@ -49,7 +49,7 @@ module internal MetadataAsSource = ProjectInfo.Create( projectId, VersionStamp.Default, - name = FSharpConstants.FSharpMetadataFilesName, + name = FSharpConstants.FSharpMetadataName + " - " + asmIdentity.Name, assemblyName = asmIdentity.Name, language = LanguageNames.FSharp, documents = [|documentInfo|], @@ -91,13 +91,14 @@ module internal MetadataAsSource = None [] -type internal FSharpMetadataAsSourceService(workspace: Workspace, projectContextFactory: IWorkspaceProjectContextFactory) = +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 <- FSharpConstants.FSharpMetadataFilesName + projectContext.DisplayName <- projInfo.Name projectContext.AddSourceFile(docInfo.FilePath, sourceCodeKind = SourceCodeKind.Regular) for metaRef in projInfo.MetadataReferences do @@ -109,14 +110,19 @@ type internal FSharpMetadataAsSourceService(workspace: Workspace, projectContext projectContext - do - workspace.DocumentClosed.Add(fun args -> - let doc = args.Document - let proj = doc.Project - if proj.Language = LanguageNames.FSharp then - match projs.TryGetValue(doc.FilePath) with - | true, projectContext -> projectContext.Dispose() - | _ -> () + 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) = @@ -134,6 +140,6 @@ type internal FSharpMetadataAsSourceService(workspace: Workspace, projectContext projs.[filePath] <- projectContext - MetadataAsSource.showDocument(filePath, Path.GetFileName(filePath) + " [Metadata]", ServiceProvider.GlobalProvider) + MetadataAsSource.showDocument(filePath, Path.GetFileName(filePath), serviceProvider) | _ -> None \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index b2a66be62d4..bb68bedf71f 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -272,7 +272,8 @@ 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.generateTemporaryDocument(AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName), targetSymbolUse.Symbol.DisplayName, originDocument.Project.MetadataReferences) + let metadataReferences = originDocument.Project.MetadataReferences + let tmpProjInfo, tmpDocId = MetadataAsSource.generateTemporaryDocument(AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName), targetSymbolUse.Symbol.DisplayName, metadataReferences) return (FSharpGoToDefinitionResult.ExternalAssembly(tmpProjInfo, tmpDocId, targetSymbolUse, targetExternalSym), idRange) | FindDeclResult.DeclFound targetRange -> diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs index 160e68118ab..42f79f405f6 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs @@ -84,9 +84,30 @@ type internal FSharpGoToDefinitionService let tmpShownDocOpt = metadataAsSourceService.ShowDocument(tmpProjInfo, tmpDocInfo.FilePath, SourceText.From(text.ToString())) match tmpShownDocOpt with | Some tmpShownDoc -> - let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, TextSpan()) - gtd.NavigateToItem(navItem, statusBar) - true + let possibleRangesAsync = + asyncMaybe { + let userOpName = "TryGoToDefinition" + let! _, _, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject (tmpShownDoc, cancellationToken, userOpName) + return! checkerProvider.Checker.FindBackgroundReferencesInFile(tmpShownDoc.FilePath, projectOptions, targetSymbolUse.Symbol, canInvalidateProject=false) |> liftAsync + } + + match Async.RunSynchronously(possibleRangesAsync, cancellationToken = cancellationToken) with + | Some possibleRanges -> + let span = + possibleRanges + |> Seq.tryHead + |> Option.map (fun r -> + match RoslynHelpers.TryFSharpRangeToTextSpan(tmpShownDoc.GetTextAsync(cancellationToken).Result, r) with + | Some span -> span + | _ -> TextSpan() + ) + |> Option.defaultValue (TextSpan()) + let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, span) + gtd.NavigateToItem(navItem, statusBar) + true + | _ -> + statusBar.TempMessage (SR.CannotDetermineSymbol()) + false | _ -> statusBar.TempMessage (SR.CannotDetermineSymbol()) false From 0fd62ae93301b6df3d463be6b56221166f3ccd79 Mon Sep 17 00:00:00 2001 From: Will Smith Date: Tue, 30 Mar 2021 21:01:40 -0700 Subject: [PATCH 6/9] Navigating to external symbol --- src/fsharp/symbols/Symbols.fs | 35 ++++++-- .../Navigation/GoToDefinition.fs | 5 +- .../Navigation/GoToDefinitionService.fs | 89 ++++++++++++++----- 3 files changed, 96 insertions(+), 33 deletions(-) diff --git a/src/fsharp/symbols/Symbols.fs b/src/fsharp/symbols/Symbols.fs index cfb813e74e1..e415b27b11b 100644 --- a/src/fsharp/symbols/Symbols.fs +++ b/src/fsharp/symbols/Symbols.fs @@ -837,6 +837,7 @@ type FSharpEntity(cenv: SymbolEnv, entity:EntityRef) = getOpenPath cpath.AccessPath [] | _ -> [] + |> List.rev let needOpenType = match entity.CompilationPathOpt with @@ -866,24 +867,40 @@ type FSharpEntity(cenv: SymbolEnv, entity:EntityRef) = 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 (FSharp.Compiler.Text.TaggedText.tagKeyword "open")) - (Layout.wordL FSharp.Compiler.Text.TaggedText.keywordType) + (Layout.wordL (TaggedText.tagKeyword "open")) + (Layout.wordL TaggedText.keywordType) else - Layout.wordL (FSharp.Compiler.Text.TaggedText.tagKeyword "open") - + Layout.wordL (TaggedText.tagKeyword "open") Layout.(^^) openKeywordL - (Layout.sepListL (Layout.sepL TaggedText.dot) openPathL) - - Layout.aboveL - (Layout.(^^) openL (Layout.sepL TaggedText.lineBreak)) - (NicePrint.layoutTycon denv infoReader AccessibleFromSomewhere range0 entity) + pathL + + Layout.aboveListL + [ + (Layout.(^^) headerL (Layout.sepL TaggedText.lineBreak)) + (Layout.(^^) openL (Layout.sepL TaggedText.lineBreak)) + (NicePrint.layoutTycon denv infoReader AccessibleFromSomewhere range0 entity) + ] |> LayoutRender.showL |> SourceText.ofString |> Some diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index bb68bedf71f..ea983e32ee3 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 * FindDeclExternalSymbol * MetadataReference seq type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpProjectOptionsManager) = let userOpName = "GoToDefinition" @@ -273,8 +273,7 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP return (FSharpGoToDefinitionResult.NavigableItem(FSharpGoToDefinitionNavigableItem(project.GetDocument(location.SourceTree), location.SourceSpan)), idRange) | _ -> let metadataReferences = originDocument.Project.MetadataReferences - let tmpProjInfo, tmpDocId = MetadataAsSource.generateTemporaryDocument(AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName), targetSymbolUse.Symbol.DisplayName, metadataReferences) - return (FSharpGoToDefinitionResult.ExternalAssembly(tmpProjInfo, tmpDocId, targetSymbolUse, targetExternalSym), idRange) + return (FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, targetExternalSym, metadataReferences), idRange) | FindDeclResult.DeclFound targetRange -> // if goto definition is called at we are alread at the declaration location of a symbol in diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs index 42f79f405f6..f6a734d88c8 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,45 +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) -> + | FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, _targetExternalSymbol, metadataReferences) -> let textOpt = match targetSymbolUse.Symbol with - | :? FSharp.Compiler.Symbols.FSharpEntity as symbol -> + | :? FSharpEntity as symbol -> symbol.TryGenerateSignatureText() - | :? FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue as symbol -> + |> Option.map (fun text -> text, symbol.DisplayName) + | :? FSharpMemberOrFunctionOrValue as symbol -> symbol.ApparentEnclosingEntity.TryGenerateSignatureText() + |> Option.map (fun text -> text, symbol.ApparentEnclosingEntity.DisplayName) | _ -> None match textOpt with - | Some text -> + | 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 possibleRangesAsync = + let goToAsync = asyncMaybe { let userOpName = "TryGoToDefinition" let! _, _, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject (tmpShownDoc, cancellationToken, userOpName) - return! checkerProvider.Checker.FindBackgroundReferencesInFile(tmpShownDoc.FilePath, projectOptions, targetSymbolUse.Symbol, canInvalidateProject=false) |> liftAsync - } + 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) - match Async.RunSynchronously(possibleRangesAsync, cancellationToken = cancellationToken) with - | Some possibleRanges -> - let span = - possibleRanges - |> Seq.tryHead - |> Option.map (fun r -> + let span = match RoslynHelpers.TryFSharpRangeToTextSpan(tmpShownDoc.GetTextAsync(cancellationToken).Result, r) with | Some span -> span | _ -> TextSpan() - ) - |> Option.defaultValue (TextSpan()) - let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, span) - gtd.NavigateToItem(navItem, statusBar) - true - | _ -> - statusBar.TempMessage (SR.CannotDetermineSymbol()) - false + + 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 From cfb49880a529aaf7f4732d5dd9d3b1a685cb0499 Mon Sep 17 00:00:00 2001 From: Will Smith Date: Wed, 31 Mar 2021 09:43:05 -0700 Subject: [PATCH 7/9] ignoring tests --- .../LegacyLanguageService/Tests.LanguageService.QuickInfo.fs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.QuickInfo.fs b/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.QuickInfo.fs index 540317a3e44..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``() = @@ -365,6 +366,7 @@ Full name: Microsoft.FSharp.Control.Async""".TrimStart().Replace("\r\n", "\n") [] [] [] + [] //This is to test when there is empty message from the TypeProviderXmlDocAttribute for TypeProvider Type member public this.``TypeProvider.XmlDocAttribute.Type.WithEmptyComment``() = @@ -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``() = @@ -523,6 +526,7 @@ Full name: Microsoft.FSharp.Control.Async""".TrimStart().Replace("\r\n", "\n") [] [] [] + [] //This is to test when there is empty message from the TypeProviderXmlDocAttribute for TypeProvider Event member public this.``TypeProvider.XmlDocAttribute.Event.WithEmptyComment``() = From 1eac4a2bc0188bad490432bb982f5fa921376004 Mon Sep 17 00:00:00 2001 From: Will Smith Date: Wed, 31 Mar 2021 09:47:37 -0700 Subject: [PATCH 8/9] Renaming TryGenerateSignatureText to TryGetMetadataText --- src/fsharp/symbols/Symbols.fs | 2 +- src/fsharp/symbols/Symbols.fsi | 4 ++-- .../FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs | 2 +- .../src/FSharp.Editor/Navigation/GoToDefinitionService.fs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/fsharp/symbols/Symbols.fs b/src/fsharp/symbols/Symbols.fs index e415b27b11b..72c72bad127 100644 --- a/src/fsharp/symbols/Symbols.fs +++ b/src/fsharp/symbols/Symbols.fs @@ -808,7 +808,7 @@ type FSharpEntity(cenv: SymbolEnv, entity:EntityRef) = member x.TryGetMembersFunctionsAndValues() = try x.MembersFunctionsAndValues with _ -> [||] :> _ - member this.TryGenerateSignatureText() = + member this.TryGetMetadataText() = match entity.TryDeref with | ValueSome entity -> let denv = DisplayEnv.Empty cenv.g diff --git a/src/fsharp/symbols/Symbols.fsi b/src/fsharp/symbols/Symbols.fsi index ffb55472318..ba09617f18b 100644 --- a/src/fsharp/symbols/Symbols.fsi +++ b/src/fsharp/symbols/Symbols.fsi @@ -359,8 +359,8 @@ type FSharpEntity = /// Safe version of `GetMembersFunctionsAndValues`. member TryGetMembersFunctionsAndValues: unit -> IList - /// Generates the signature of the symbol as source text. - member TryGenerateSignatureText: unit -> ISourceText option + /// 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 [] diff --git a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs index c9ca1cfdbc4..f5e05e7cda1 100644 --- a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs +++ b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs @@ -3821,7 +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] TryGenerateSignatureText() +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/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs index f6a734d88c8..c69f46ec0f3 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs @@ -74,10 +74,10 @@ type internal FSharpGoToDefinitionService let textOpt = match targetSymbolUse.Symbol with | :? FSharpEntity as symbol -> - symbol.TryGenerateSignatureText() + symbol.TryGetMetadataText() |> Option.map (fun text -> text, symbol.DisplayName) | :? FSharpMemberOrFunctionOrValue as symbol -> - symbol.ApparentEnclosingEntity.TryGenerateSignatureText() + symbol.ApparentEnclosingEntity.TryGetMetadataText() |> Option.map (fun text -> text, symbol.ApparentEnclosingEntity.DisplayName) | _ -> None From 68df78fb3e22fe02b158bc8287718c9682249d1b Mon Sep 17 00:00:00 2001 From: Will Smith Date: Wed, 31 Mar 2021 11:29:07 -0700 Subject: [PATCH 9/9] Fixing namespace and some parts of module --- src/fsharp/NicePrint.fs | 13 +++ src/fsharp/NicePrint.fsi | 2 + src/fsharp/symbols/Symbols.fs | 5 +- .../src/FSharp.Editor/Common/Extensions.fs | 5 +- .../Diagnostics/DocumentDiagnosticAnalyzer.fs | 3 + .../Navigation/GoToDefinition.fs | 105 +++++++++--------- .../Navigation/GoToDefinitionService.fs | 2 +- 7 files changed, 80 insertions(+), 55 deletions(-) diff --git a/src/fsharp/NicePrint.fs b/src/fsharp/NicePrint.fs index 39dc0e86444..b8f49aa4553 100755 --- a/src/fsharp/NicePrint.fs +++ b/src/fsharp/NicePrint.fs @@ -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 72c72bad127..b9d42526cc4 100644 --- a/src/fsharp/symbols/Symbols.fs +++ b/src/fsharp/symbols/Symbols.fs @@ -811,6 +811,9 @@ type FSharpEntity(cenv: SymbolEnv, entity:EntityRef) = 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 @@ -899,7 +902,7 @@ type FSharpEntity(cenv: SymbolEnv, entity:EntityRef) = [ (Layout.(^^) headerL (Layout.sepL TaggedText.lineBreak)) (Layout.(^^) openL (Layout.sepL TaggedText.lineBreak)) - (NicePrint.layoutTycon denv infoReader AccessibleFromSomewhere range0 entity) + (NicePrint.layoutEntity denv infoReader AccessibleFromSomewhere range0 entity) ] |> LayoutRender.showL |> SourceText.ofString diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 0adb75753bf..825a010f387 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -40,9 +40,8 @@ type ProjectId with type Project with member this.IsFSharpMiscellaneous = this.Name = FSharpConstants.FSharpMiscellaneousFilesName - member this.IsFSharpMiscellaneousOrMetadata = - this.IsFSharpMiscellaneous || - this.Name.StartsWith(FSharpConstants.FSharpMetadataName) + 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>() = diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs index 57fca069cba..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! diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index ea983e32ee3..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 FSharpSymbolUse * FindDeclExternalSymbol * MetadataReference seq + | ExternalAssembly of FSharpSymbolUse * MetadataReference seq type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpProjectOptionsManager) = let userOpName = "GoToDefinition" @@ -273,62 +273,67 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP return (FSharpGoToDefinitionResult.NavigableItem(FSharpGoToDefinitionNavigableItem(project.GetDocument(location.SourceTree), location.SourceSpan)), idRange) | _ -> let metadataReferences = originDocument.Project.MetadataReferences - return (FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, targetExternalSym, metadataReferences), idRange) + 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 c69f46ec0f3..8af615f8b07 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs @@ -70,7 +70,7 @@ type internal FSharpGoToDefinitionService gtd.NavigateToItem(navItem, statusBar) // 'true' means do it, like Sheev Palpatine would want us to. true - | FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, _targetExternalSymbol, metadataReferences) -> + | FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences) -> let textOpt = match targetSymbolUse.Symbol with | :? FSharpEntity as symbol ->