From 273d40378db36715e4cb0bbface1c92ec94da4c4 Mon Sep 17 00:00:00 2001 From: Kevin Ransom Date: Wed, 18 Oct 2017 17:08:15 -0700 Subject: [PATCH 1/6] P2p references --- src/fsharp/vs/service.fs | 16 +- .../Classification/ColorizationService.fs | 2 +- .../Diagnostics/DocumentDiagnosticAnalyzer.fs | 2 +- .../LanguageService/LanguageService.fs | 168 +++------- .../LanguageService/SymbolHelpers.fs | 2 +- .../Navigation/FindUsagesService.fs | 4 +- .../Navigation/NavigateToSearchService.fs | 4 +- .../QuickInfo/QuickInfoProvider.fs | 2 +- .../BackgroundRequests.fs | 8 +- .../FSharp.LanguageService/IProjectSite.fs | 6 +- .../ProjectSitesAndFiles.fs | 304 +++++++++++++----- .../Salsa/FSharpLanguageServiceTestable.fs | 5 +- .../tests/Salsa/VisualFSharp.Salsa.fsproj | 4 + 13 files changed, 301 insertions(+), 226 deletions(-) diff --git a/src/fsharp/vs/service.fs b/src/fsharp/vs/service.fs index 5c2fc8f5445..2156196307c 100644 --- a/src/fsharp/vs/service.fs +++ b/src/fsharp/vs/service.fs @@ -1373,8 +1373,8 @@ type FSharpParsingOptions = } member x.LastFileName = - Debug.Assert(not (Array.isEmpty x.SourceFiles), "Parsing options don't contain any file") - Array.last x.SourceFiles + if x.SourceFiles.Length = 0 then "" + else Array.last x.SourceFiles static member Default = { SourceFiles = Array.empty @@ -1724,7 +1724,7 @@ type FSharpProjectOptions = member x.ProjectOptions = x.OtherOptions /// Whether the two parse options refer to the same project. static member UseSameProjectFileName(options1,options2) = - options1.ProjectFileName = options2.ProjectFileName + options1.ProjectFileName = options2.ProjectFileName /// Compare two options sets with respect to the parts of the options that are important to building. static member AreSameForChecking(options1,options2) = @@ -1737,7 +1737,9 @@ type FSharpProjectOptions = options1.UnresolvedReferences = options2.UnresolvedReferences && options1.OriginalLoadReferences = options2.OriginalLoadReferences && options1.ReferencedProjects.Length = options2.ReferencedProjects.Length && - Array.forall2 (fun (n1,a) (n2,b) -> n1 = n2 && FSharpProjectOptions.AreSameForChecking(a,b)) options1.ReferencedProjects options2.ReferencedProjects && + Array.forall2 (fun (n1,a) (n2,b) -> + n1 = n2 && + FSharpProjectOptions.AreSameForChecking(a,b)) options1.ReferencedProjects options2.ReferencedProjects && options1.LoadTime = options2.LoadTime /// Compute the project directory. @@ -2043,11 +2045,11 @@ module Helpers = // Look for DLLs in the location of the service DLL first. let defaultFSharpBinariesDir = FSharpEnvironment.BinFolderOfDefaultFSharpCompiler(Some(typeof.Assembly.Location)).Value - + /// Determine whether two (fileName,options) keys are identical w.r.t. affect on checking - let AreSameForChecking2((fileName1: string, options1: FSharpProjectOptions), (fileName2, o2)) = + let AreSameForChecking2((fileName1: string, options1: FSharpProjectOptions), (fileName2, options2)) = (fileName1 = fileName2) - && FSharpProjectOptions.AreSameForChecking(options1,o2) + && FSharpProjectOptions.AreSameForChecking(options1,options2) /// Determine whether two (fileName,options) keys should be identical w.r.t. resource usage let AreSubsumable2((fileName1:string,o1:FSharpProjectOptions),(fileName2:string,o2:FSharpProjectOptions)) = diff --git a/vsintegration/src/FSharp.Editor/Classification/ColorizationService.fs b/vsintegration/src/FSharp.Editor/Classification/ColorizationService.fs index b60ee3b9bcc..c3b199d4be3 100644 --- a/vsintegration/src/FSharp.Editor/Classification/ColorizationService.fs +++ b/vsintegration/src/FSharp.Editor/Classification/ColorizationService.fs @@ -40,7 +40,7 @@ type internal FSharpColorizationService asyncMaybe { do Trace.TraceInformation("{0:n3} (start) SemanticColorization", DateTime.Now.TimeOfDay.TotalSeconds) do! Async.Sleep DefaultTuning.SemanticColorizationInitialDelay |> liftAsync // be less intrusive, give other work priority most of the time - let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject(document) + let! _, _, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject(document) let! sourceText = document.GetTextAsync(cancellationToken) let! _, _, checkResults = checkerProvider.Checker.ParseAndCheckDocument(document, projectOptions, sourceText = sourceText, allowStaleResults = false, userOpName=userOpName) // it's crucial to not return duplicated or overlapping `ClassifiedSpan`s because Find Usages service crashes. diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs index ffcf5c6ef83..5b8b1744aeb 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs @@ -122,7 +122,7 @@ type internal FSharpDocumentDiagnosticAnalyzer() = override this.AnalyzeSemanticsAsync(document: Document, cancellationToken: CancellationToken): Task> = let projectInfoManager = getProjectInfoManager document asyncMaybe { - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject(document) + let! parsingOptions, _, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject(document) let! sourceText = document.GetTextAsync(cancellationToken) let! textVersion = document.GetTextVersionAsync(cancellationToken) return! diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index 238325be41b..770af78a58d 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -18,7 +18,7 @@ open System.Threading open Microsoft.FSharp.Compiler.CompileOps open Microsoft.FSharp.Compiler.SourceCodeServices -open Microsoft.FSharp.Compiler.AbstractIL.Internal.Library +open Microsoft.VisualStudio.FSharp.LanguageService.SiteProvider open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Diagnostics @@ -83,9 +83,6 @@ type internal FSharpCheckerProvider member this.Checker = checker.Value -/// A value and a function to recompute/refresh the value. The function is passed a flag indicating if a refresh is happening. -type Refreshable<'T> = 'T * (bool -> 'T) - /// Exposes FCS FSharpProjectOptions information management as MEF component. // // This service allows analyzers to get an appropriate FSharpProjectOptions value for a project or single file. @@ -101,49 +98,31 @@ type internal FSharpProjectOptionsManager ) = // A table of information about projects, excluding single-file projects. - let projectTable = ConcurrentDictionary>() + let projectOptionsTable = FSharpProjectOptionsTable() // A table of information about single-file projects. Currently we only need the load time of each such file, plus // the original options for editing let singleFileProjectTable = ConcurrentDictionary() - // Accumulate sources and references for each project file - let projectInfo = new ConcurrentDictionary() - - let projectDisplayNameOf projectFileName = - if String.IsNullOrWhiteSpace projectFileName then projectFileName - else Path.GetFileNameWithoutExtension projectFileName - - let tryGetOrCreateProjectId (projectFileName: string) = + let tryGetOrCreateProjectId (projectFileName:string) = let projectDisplayName = projectDisplayNameOf projectFileName Some (workspace.ProjectTracker.GetOrCreateProjectIdForPath(projectFileName, projectDisplayName)) + /// Retrieve the projectOptionsTable + member __.FSharpOptions = projectOptionsTable + /// Clear a project from the project table - member this.ClearInfoForProject(projectId: ProjectId) = - projectTable.TryRemove(projectId) |> ignore - this.RefreshInfoForProjectsThatReferenceThisProject(projectId) + member this.ClearInfoForProject(projectId:ProjectId) = projectOptionsTable.ClearInfoForProject(projectId) member this.ClearInfoForSingleFileProject(projectId) = singleFileProjectTable.TryRemove(projectId) |> ignore - member this.RefreshInfoForProjectsThatReferenceThisProject(projectId: ProjectId) = - // Search the projectTable for things to refresh - for KeyValue(otherProjectId, ((referencedProjectIds, _parsingOptions, _options), refresh)) in projectTable.ToArray() do - for referencedProjectId in referencedProjectIds do - if referencedProjectId = projectId then - projectTable.[otherProjectId] <- (refresh true, refresh) - - member this.AddOrUpdateProject(projectId, refresh) = - projectTable.[projectId] <- (refresh false, refresh) - this.RefreshInfoForProjectsThatReferenceThisProject(projectId) - - member this.AddOrUpdateSingleFileProject(projectId, data) = - singleFileProjectTable.[projectId] <- data + member this.AddOrUpdateSingleFileProject(projectId, data) = singleFileProjectTable.[projectId] <- data /// Get the exact options for a single-file script member this.ComputeSingleFileOptions (tryGetOrCreateProjectId, fileName, loadTime, fileContents, workspace: Workspace) = async { let extraProjectInfo = Some(box workspace) - let tryGetOptionsForReferencedProject f = f |> tryGetOrCreateProjectId |> Option.bind this.TryGetOptionsForProject |> Option.map snd + let tryGetOptionsForReferencedProject f = f |> tryGetOrCreateProjectId |> Option.bind this.TryGetOptionsForProject |> Option.map(fun (_, _, projectOptions) -> projectOptions) if SourceFile.MustBeSingleFileProject(fileName) then // NOTE: we don't use a unique stamp for single files, instead comparing options structurally. // This is because we repeatedly recompute the options. @@ -153,27 +132,27 @@ type internal FSharpProjectOptionsManager // compiled and #r will refer to files on disk let referencedProjectFileNames = [| |] let site = ProjectSitesAndFiles.CreateProjectSiteForScript(fileName, referencedProjectFileNames, options) - let deps, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject,site,fileName,options.ExtraProjectInfo,serviceProvider, true) + let deps, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, serviceProvider, (tryGetOrCreateProjectId fileName), fileName, options.ExtraProjectInfo, Some projectOptionsTable, true) let parsingOptions, _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions(projectOptions) return (deps, parsingOptions, projectOptions) else let site = ProjectSitesAndFiles.ProjectSiteOfSingleFile(fileName) - let deps, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject,site,fileName,extraProjectInfo,serviceProvider, true) + let deps, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, serviceProvider, (tryGetOrCreateProjectId fileName), fileName, extraProjectInfo, Some projectOptionsTable, true) let parsingOptions, _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions(projectOptions) return (deps, parsingOptions, projectOptions) } /// Update the info for a project in the project table - member this.UpdateProjectInfo(tryGetOrCreateProjectId, projectId: ProjectId, site: IProjectSite, userOpName) = - this.AddOrUpdateProject(projectId, (fun isRefresh -> + member this.UpdateProjectInfo(tryGetOrCreateProjectId, projectId: ProjectId, site:IProjectSite, userOpName) = + projectOptionsTable.AddOrUpdateProject(projectId, (fun isRefresh -> let extraProjectInfo = Some(box workspace) - let tryGetOptionsForReferencedProject f = f |> tryGetOrCreateProjectId |> Option.bind this.TryGetOptionsForProject |> Option.map snd - let referencedProjects, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, site.ProjectFileName, extraProjectInfo, serviceProvider, true) + let tryGetOptionsForReferencedProject f = f |> tryGetOrCreateProjectId |> Option.bind this.TryGetOptionsForProject |> Option.map(fun (_, _, projectOptions) -> projectOptions) + let referencedProjects, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, serviceProvider, (tryGetOrCreateProjectId (site.ProjectFileName)), site.ProjectFileName, extraProjectInfo, Some projectOptionsTable, true) let referencedProjectIds = referencedProjects |> Array.choose tryGetOrCreateProjectId - checkerProvider.Checker.InvalidateConfiguration(projectOptions, startBackgroundCompileIfAlreadySeen = not isRefresh, userOpName= userOpName + ".UpdateProjectInfo") + checkerProvider.Checker.InvalidateConfiguration(projectOptions, startBackgroundCompileIfAlreadySeen = not isRefresh, userOpName = userOpName + ".UpdateProjectInfo") let parsingOptions, _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions(projectOptions) - referencedProjectIds, parsingOptions, projectOptions)) - + referencedProjectIds, parsingOptions, Some site, projectOptions)) + /// Get compilation defines relevant for syntax processing. /// Quicker then TryGetOptionsForDocumentOrProject as it doesn't need to recompute the exact project /// options for a script. @@ -181,20 +160,16 @@ type internal FSharpProjectOptionsManager let projectOptionsOpt = this.TryGetOptionsForProject(document.Project.Id) let parsingOptions = match projectOptionsOpt with - | None -> FSharpParsingOptions.Default - | Some (parsingOptions, _projectOptions) -> parsingOptions + | Some (parsingOptions, _site, _projectOptions) -> parsingOptions + | _ -> FSharpParsingOptions.Default CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, parsingOptions) - /// Get the options for a project - member this.TryGetOptionsForProject(projectId: ProjectId) = - match projectTable.TryGetValue(projectId) with - | true, ((_referencedProjects, parsingOptions, projectOptions), _) -> Some (parsingOptions, projectOptions) - | _ -> None + member this.TryGetOptionsForProject(projectId:ProjectId) = projectOptionsTable.TryGetOptionsForProject(projectId) /// Get the exact options for a document or project member this.TryGetOptionsForDocumentOrProject(document: Document) = async { let projectId = document.Project.Id - + // The options for a single-file script project are re-requested each time the file is analyzed. This is because the // single-file project may contain #load and #r references which are changing as the user edits, and we may need to re-analyze // to determine the latest settings. FCS keeps a cache to help ensure these are up-to-date. @@ -209,11 +184,11 @@ type internal FSharpProjectOptionsManager let tryGetOrCreateProjectId _ = None let! _referencedProjectFileNames, parsingOptions, projectOptions = this.ComputeSingleFileOptions (tryGetOrCreateProjectId, fileName, loadTime, sourceText.ToString(), document.Project.Solution.Workspace) this.AddOrUpdateSingleFileProject(projectId, (loadTime, parsingOptions, projectOptions)) - return Some (parsingOptions, projectOptions) + return Some (parsingOptions, None, projectOptions) with ex -> Assert.Exception(ex) return None - | _ -> return this.TryGetOptionsForProject(projectId) + | _ -> return this.TryGetOptionsForProject(projectId) } /// Get the options for a document or project relevant for syntax processing. @@ -222,75 +197,23 @@ type internal FSharpProjectOptionsManager let projectId = document.Project.Id match singleFileProjectTable.TryGetValue(projectId) with | true, (_loadTime, parsingOptions, originalOptions) -> Some (parsingOptions, originalOptions) - | _ -> this.TryGetOptionsForProject(projectId) - - member this.ProvideProjectSiteProvider(project:Project) = - let hier = workspace.GetHierarchy(project.Id) - - {new IProvideProjectSite with - member iProvideProjectSite.GetProjectSite() = - let fst (a, _, _) = a - let thrd (_, _, c) = c - let mutable errorReporter = - let reporter = ProjectExternalErrorReporter(project.Id, "FS", serviceProvider) - Some(reporter:> Microsoft.VisualStudio.Shell.Interop.IVsLanguageServiceBuildErrorReporter2) - - {new Microsoft.VisualStudio.FSharp.LanguageService.IProjectSite with - member __.CompilationSourceFiles = this.GetProjectInfo(project.FilePath) |> fst - member __.CompilationOptions = - let _,references,options = this.GetProjectInfo(project.FilePath) - Array.concat [options; references |> Array.map(fun r -> "-r:" + r)] - member __.CompilationReferences = this.GetProjectInfo(project.FilePath) |> thrd - member site.CompilationBinOutputPath = site.CompilationOptions |> Array.tryPick (fun s -> if s.StartsWith("-o:") then Some s.[3..] else None) - member __.Description = project.Name - member __.ProjectFileName = project.FilePath - member __.AdviseProjectSiteChanges(_,_) = () - member __.AdviseProjectSiteCleaned(_,_) = () - member __.AdviseProjectSiteClosed(_,_) = () - member __.IsIncompleteTypeCheckEnvironment = false - member __.TargetFrameworkMoniker = "" - member __.ProjectGuid = project.Id.Id.ToString() - member __.LoadTime = System.DateTime.Now - member __.ProjectProvider = Some iProvideProjectSite - member __.BuildErrorReporter with get () = errorReporter and - set (v) = errorReporter <- v - } - - // TODO: figure out why this is necessary - interface IVsHierarchy with - member __.SetSite(psp) = hier.SetSite(psp) - member __.GetSite(psp) = hier.GetSite(ref psp) - member __.QueryClose(pfCanClose) = hier.QueryClose(ref pfCanClose) - member __.Close() = hier.Close() - member __.GetGuidProperty(itemid, propid, pguid) = hier.GetGuidProperty(itemid, propid, ref pguid) - member __.SetGuidProperty(itemid, propid, rguid) = hier.SetGuidProperty(itemid, propid, ref rguid) - member __.GetProperty(itemid, propid, pvar) = hier.GetProperty(itemid, propid, ref pvar) - member __.SetProperty(itemid, propid, var) = hier.SetProperty(itemid, propid, var) - member __.GetNestedHierarchy(itemid, iidHierarchyNested, ppHierarchyNested, pitemidNested) = hier.GetNestedHierarchy(itemid, ref iidHierarchyNested, ref ppHierarchyNested, ref pitemidNested) - member __.GetCanonicalName(itemid, pbstrName) = hier.GetCanonicalName(itemid, ref pbstrName) - member __.ParseCanonicalName(pszName, pitemid) = hier.ParseCanonicalName(pszName, ref pitemid) - member __.Unused0() = hier.Unused0() - member __.AdviseHierarchyEvents(pEventSink, pdwCookie) = hier.AdviseHierarchyEvents(pEventSink, ref pdwCookie) - member __.UnadviseHierarchyEvents(dwCookie) = hier.UnadviseHierarchyEvents(dwCookie) - member __.Unused1() = hier.Unused1() - member __.Unused2() = hier.Unused2() - member __.Unused3() = hier.Unused3() - member __.Unused4() = hier.Unused4() - } + | _ -> this.TryGetOptionsForProject(projectId) |> Option.map(fun (parsingOptions, _, projectOptions) -> parsingOptions, projectOptions) + /// get a siteprovider + member this.ProvideProjectSiteProvider(project:Project) = provideProjectSiteProvider(workspace, project, serviceProvider, Some projectOptionsTable) + + /// Tell the checker to update the project info for the specified project id member this.UpdateProjectInfoWithProjectId(projectId:ProjectId, userOpName) = let hier = workspace.GetHierarchy(projectId) match hier with + | null -> () | h when (h.IsCapabilityMatch("CPS")) -> let project = workspace.CurrentSolution.GetProject(projectId) - let siteProvider = this.ProvideProjectSiteProvider(project) - this.UpdateProjectInfo(tryGetOrCreateProjectId, projectId, siteProvider.GetProjectSite(), userOpName) + if not (isNull project) then + let siteProvider = provideProjectSiteProvider(workspace, project, serviceProvider, Some projectOptionsTable) + this.UpdateProjectInfo(tryGetOrCreateProjectId, projectId, siteProvider.GetProjectSite(), userOpName) | _ -> () - member this.UpdateProjectInfoWithPath(path, userOpName) = - let projectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath(path, projectDisplayNameOf path) - this.UpdateProjectInfoWithProjectId(projectId, userOpName) - [] /// This handles commandline change notifications from the Dotnet Project-system member this.HandleCommandLineChanges(path:string, sources:ImmutableArray, references:ImmutableArray, options:ImmutableArray) = @@ -299,13 +222,9 @@ type internal FSharpProjectOptionsManager else Path.Combine(Path.GetDirectoryName(path), p) let sourcePaths = sources |> Seq.map(fun s -> fullPath s.Path) |> Seq.toArray let referencePaths = references |> Seq.map(fun r -> fullPath r.Reference) |> Seq.toArray - projectInfo.[path] <- (sourcePaths,referencePaths,options.ToArray()) - this.UpdateProjectInfoWithPath(path, "HandleCommandLineChanges") - - member __.GetProjectInfo(path:string) = - match projectInfo.TryGetValue path with - | true, value -> value - | _ -> [||], [||], [||] + let projectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath(path, projectDisplayNameOf path) + if projectOptionsTable.SetOptionsWithProjectId(projectId, sourcePaths,referencePaths,options.ToArray()) then + this.UpdateProjectInfoWithProjectId(projectId, "HandleCommandLineChanges") member __.Checker = checkerProvider.Checker @@ -419,13 +338,18 @@ type let optionsAssociation = ConditionalWeakTable() - member private this.OnProjectAdded(projectId:ProjectId, _newSolution:Solution) = projectInfoManager.UpdateProjectInfoWithProjectId(projectId, "OnProjectAdded") - override this.Initialize() = + member private this.OnProjectAdded(projectId:ProjectId) = projectInfoManager.UpdateProjectInfoWithProjectId(projectId, "OnProjectAdded") + member private this.OnProjectChanged(projectId:ProjectId) = projectInfoManager.UpdateProjectInfoWithProjectId(projectId, "OnProjectChanged") + member private this.OnProjectReloaded(projectId:ProjectId) = projectInfoManager.UpdateProjectInfoWithProjectId(projectId, "OnProjectReloaded") + + override this.Initialize() = base.Initialize() let workspaceChanged (args:WorkspaceChangeEventArgs) = match args.Kind with - | WorkspaceChangeKind.ProjectAdded -> this.OnProjectAdded(args.ProjectId, args.NewSolution) + | WorkspaceChangeKind.ProjectAdded -> this.OnProjectAdded(args.ProjectId) + | WorkspaceChangeKind.ProjectChanged -> this.OnProjectChanged(args.ProjectId) + | WorkspaceChangeKind.ProjectReloaded -> this.OnProjectReloaded(args.ProjectId) | _ -> () this.Workspace.Options <- this.Workspace.Options.WithChangedOption(Completion.CompletionOptions.BlockForCompletionItems, FSharpConstants.FSharpLanguageName, false) @@ -553,7 +477,7 @@ type // Roslyn is expecting site to be an IVsHierarchy. // It just so happens that the object that implements IProvideProjectSite is also // an IVsHierarchy. This assertion is to ensure that the assumption holds true. - Debug.Assert(hierarchy <> null, "About to CreateProjectContext with a non-hierarchy site") + Debug.Assert(not (isNull hierarchy), "About to CreateProjectContext with a non-hierarchy site") let projectContext = projectContextFactory.CreateProjectContext( @@ -582,7 +506,7 @@ type optionsAssociation.Remove(projectContext) |> ignore project.Disconnect())) - for referencedSite in ProjectSitesAndFiles.GetReferencedProjectSites (site, this.SystemServiceProvider) do + for referencedSite in ProjectSitesAndFiles.GetReferencedProjectSites(site, this.SystemServiceProvider, Some (this.Workspace :>obj), Some projectInfoManager.FSharpOptions ) do setup referencedSite setup (siteProvider.GetProjectSite()) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs index 15995d015f0..b2ad08f76ad 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs @@ -59,7 +59,7 @@ module internal SymbolHelpers = |> Seq.map (fun project -> async { match projectInfoManager.TryGetOptionsForProject(project.Id) with - | Some (_parsingOptions, projectOptions) -> + | Some (_parsingOptions, _site, projectOptions) -> let! projectCheckResults = checker.ParseAndCheckProject(projectOptions, userOpName = userOpName) return! projectCheckResults.GetUsesOfSymbol(symbol) | None -> return [||] diff --git a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs index e3479737db0..0f7fde12e79 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs @@ -51,7 +51,7 @@ type internal FSharpFindUsagesService asyncMaybe { let! sourceText = document.GetTextAsync(context.CancellationToken) |> Async.AwaitTask |> liftAsync let checker = checkerProvider.Checker - let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject(document) + let! parsingOptions, _, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject(document) let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, projectOptions, sourceText = sourceText, allowStaleResults = true, userOpName = userOpName) let textLine = sourceText.Lines.GetLineFromPosition(position).ToString() let lineNumber = sourceText.Lines.GetLinePosition(position).Line + 1 @@ -112,7 +112,7 @@ type internal FSharpFindUsagesService projectsToCheck |> Seq.map (fun project -> asyncMaybe { - let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForProject(project.Id) + let! _parsingOptions, _site, projectOptions = projectInfoManager.TryGetOptionsForProject(project.Id) let! projectCheckResults = checker.ParseAndCheckProject(projectOptions, userOpName = userOpName) |> liftAsync return! projectCheckResults.GetUsesOfSymbol(symbolUse.Symbol) |> liftAsync } |> Async.map (Option.defaultValue [||])) diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs index a1cee3bff49..38c2abc9687 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs @@ -233,7 +233,7 @@ type internal FSharpNavigateToSearchService interface INavigateToSearchService with member __.SearchProjectAsync(project, searchPattern, cancellationToken) : Task> = asyncMaybe { - let! parsingOptions, _options = projectInfoManager.TryGetOptionsForProject(project.Id) + let! parsingOptions, _site, _options = projectInfoManager.TryGetOptionsForProject(project.Id) let! items = project.Documents |> Seq.map (fun document -> getCachedIndexedNavigableItems(document, parsingOptions)) @@ -265,7 +265,7 @@ type internal FSharpNavigateToSearchService member __.SearchDocumentAsync(document, searchPattern, cancellationToken) : Task> = asyncMaybe { - let! parsingOptions, _options = projectInfoManager.TryGetOptionsForDocumentOrProject(document) + let! parsingOptions, _, _ = projectInfoManager.TryGetOptionsForDocumentOrProject(document) let! items = getCachedIndexedNavigableItems(document, parsingOptions) |> liftAsync return items.Find(searchPattern) } diff --git a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs index 8e4a6fa82a0..802d8948ca5 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs @@ -55,7 +55,7 @@ module private FSharpQuickInfo = let extLineText = (extSourceText.Lines.GetLineFromPosition extSpan.Start).ToString() // project options need to be retrieved because the signature file could be in another project - let! extParsingOptions, extProjectOptions = projectInfoManager.TryGetOptionsForProject extDocId.ProjectId + let! extParsingOptions, _extSite, extProjectOptions = projectInfoManager.TryGetOptionsForProject extDocId.ProjectId let extDefines = CompilerEnvironment.GetCompilationDefinesForEditing (extDocument.FilePath, extParsingOptions) let! extLexerSymbol = Tokenizer.getSymbolAtPosition(extDocId, extSourceText, extSpan.Start, declRange.FileName, extDefines, SymbolLookupKind.Greedy, true) let! _, _, extCheckFileResults = checker.ParseAndCheckDocument(extDocument, extProjectOptions, allowStaleResults=true, sourceText=extSourceText, userOpName = userOpName) diff --git a/vsintegration/src/FSharp.LanguageService/BackgroundRequests.fs b/vsintegration/src/FSharp.LanguageService/BackgroundRequests.fs index 36404fd5eb8..42c673d90b7 100644 --- a/vsintegration/src/FSharp.LanguageService/BackgroundRequests.fs +++ b/vsintegration/src/FSharp.LanguageService/BackgroundRequests.fs @@ -7,14 +7,10 @@ namespace Microsoft.VisualStudio.FSharp.LanguageService open System -open System.Runtime.InteropServices -open Microsoft.VisualStudio open Microsoft.VisualStudio.TextManager.Interop open Microsoft.VisualStudio.Text -open Microsoft.VisualStudio.OLE.Interop -open Microsoft.VisualStudio.Shell.Interop -open Microsoft.FSharp.Compiler open Microsoft.FSharp.Compiler.SourceCodeServices +open Microsoft.VisualStudio.FSharp.LanguageService.SiteProvider #nowarn "44" // use of obsolete CheckFileInProjectAllowingStaleCachedResults @@ -113,7 +109,7 @@ type internal FSharpLanguageServiceBackgroundRequests_DEPRECATED let rdt = getServiceProvider().RunningDocumentTable let projectSite = getProjectSitesAndFiles().FindOwningProject_DEPRECATED(rdt,fileName) let enableInMemoryCrossProjectReferences = true - let _, checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, (fun _ -> None), projectSite, fileName, None, getServiceProvider(), false) + let _, checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, (fun _ -> None), projectSite, getServiceProvider(), None(*projectId*), fileName, None(*extraProjectInfo*), None(*FSharpProjectOptionsTable*), false) let projectFileName = projectSite.ProjectFileName let data = { ProjectSite = projectSite diff --git a/vsintegration/src/FSharp.LanguageService/IProjectSite.fs b/vsintegration/src/FSharp.LanguageService/IProjectSite.fs index dfdc99d8ec4..18e75ee37db 100644 --- a/vsintegration/src/FSharp.LanguageService/IProjectSite.fs +++ b/vsintegration/src/FSharp.LanguageService/IProjectSite.fs @@ -29,6 +29,9 @@ and internal IProjectSite = /// The '-o:' output bin path, without the '-o:' abstract CompilationBinOutputPath : string option + /// The name of the project file. + abstract ProjectFileName : string + /// Register for notifications for when the above change abstract AdviseProjectSiteChanges : callbackOwnerKey: string * AdviseProjectSiteChanges -> unit @@ -41,9 +44,6 @@ and internal IProjectSite = /// A user-friendly description of the project. Used only for developer/DEBUG tooltips and such. abstract Description : string - /// The name of the project file. - abstract ProjectFileName : string - /// The error list task reporter abstract BuildErrorReporter : Microsoft.VisualStudio.Shell.Interop.IVsLanguageServiceBuildErrorReporter2 option with get, set diff --git a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs index 41269b8bab4..e037eb79fe2 100644 --- a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs +++ b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs @@ -7,7 +7,7 @@ // information is conveyed to the rest of the implementation via an IProjectSite interface. // // For most purposes, an IProjectSite has to provide three main things -// - the source files +// - the source files` // - the compilation options // - the assembly references. // Project.fs collects the first two from MSBuild. For the third - assembly references - it looks @@ -30,29 +30,47 @@ // This means a lot of the stuff above is irrelevant in that case, apart from where FSharpProjectOptionsManager // incrementally maintains a corresponding F# CompilerService FSharpProjectOptions value. -namespace Microsoft.VisualStudio.FSharp.LanguageService +module internal rec Microsoft.VisualStudio.FSharp.LanguageService.SiteProvider open System +open System.Collections.Concurrent +open System.ComponentModel.Composition open System.IO open System.Diagnostics -open System.Runtime.InteropServices open Microsoft.VisualStudio open Microsoft.VisualStudio.TextManager.Interop open Microsoft.VisualStudio.Shell.Interop open Microsoft.FSharp.Compiler.SourceCodeServices +open Microsoft.CodeAnalysis +open Microsoft.VisualStudio.LanguageServices +open Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +open Microsoft.VisualStudio.LanguageServices.Implementation.TaskList +open VSLangProj +open System.ComponentModel.Composition.Primitives +open Microsoft.VisualStudio.Shell +open System.Collections.Immutable + + /// An additional interface that an IProjectSite object can implement to indicate it has an FSharpProjectOptions /// already available, so we don't have to recreate it type private IHaveCheckOptions = abstract OriginalCheckOptions : unit -> string[] * FSharpProjectOptions - + +let projectDisplayNameOf projectFileName = + if String.IsNullOrWhiteSpace projectFileName then projectFileName + else Path.GetFileNameWithoutExtension projectFileName + +/// A value and a function to recompute/refresh the value. The function is passed a flag indicating if a refresh is happening. +type Refreshable<'T> = 'T * (bool -> 'T) + /// Convert from FSharpProjectOptions into IProjectSite. -type private ProjectSiteOfScriptFile(filename:string, referencedProjectFileNames, checkOptions : FSharpProjectOptions) = +type private ProjectSiteOfScriptFile(filename:string, referencedProjectFileNames, checkOptions: FSharpProjectOptions) = interface IProjectSite with override this.Description = sprintf "Script Closure at Root %s" filename override this.CompilationSourceFiles = checkOptions.SourceFiles override this.CompilationOptions = checkOptions.OtherOptions - override this.CompilationReferences = + override this.CompilationReferences = checkOptions.OtherOptions |> Array.choose (fun flag -> if flag.StartsWith("-r:") then Some flag.[3..] else None) override this.CompilationBinOutputPath = None @@ -69,21 +87,23 @@ type private ProjectSiteOfScriptFile(filename:string, referencedProjectFileNames interface IHaveCheckOptions with override this.OriginalCheckOptions() = (referencedProjectFileNames, checkOptions) - + override x.ToString() = sprintf "ProjectSiteOfScriptFile(%s)" filename - + /// An orphan file project is a .fs, .ml, .fsi, .mli that is not associated with a .fsproj. /// By design, these are never going to typecheck because there is no affiliated references. /// We show many squiggles in this case because they're not particularly informational. -type private ProjectSiteOfSingleFile(sourceFile) = - // CompilationOptions gets called a lot, so pre-compute what we can +type private ProjectSiteOfSingleFile(sourceFile) = + // CompilerFlags() gets called a lot, so pre-compute what we can static let compilerFlags = let flags = ["--noframework";"--warn:3"] let assumeDotNetFramework = true let defaultReferences = [ for r in CompilerEnvironment.DefaultReferencesForOrphanSources(assumeDotNetFramework) do yield sprintf "-r:%s%s" r (if r.EndsWith(".dll",StringComparison.OrdinalIgnoreCase) then "" else ".dll") ] - (flags @ defaultReferences) |> List.toArray + (flags @ defaultReferences) + |> List.toArray + |> Array.choose (fun flag -> if flag.StartsWith("-r:") then Some flag.[3..] elif flag.StartsWith("--reference:") then Some flag.[12..] else None) let projectFileName = sourceFile + ".orphan.fsproj" @@ -91,9 +111,9 @@ type private ProjectSiteOfSingleFile(sourceFile) = override this.Description = projectFileName override this.CompilationSourceFiles = [|sourceFile|] override this.CompilationOptions = compilerFlags - override this.CompilationReferences = compilerFlags |> Array.choose (fun flag -> if flag.StartsWith("-r:") then Some flag.[3..] else None) + override this.CompilationReferences = compilerFlags override this.CompilationBinOutputPath = None - override this.ProjectFileName = projectFileName + override this.ProjectFileName = projectFileName override this.BuildErrorReporter with get() = None and set _v = () override this.AdviseProjectSiteChanges(_,_) = () override this.AdviseProjectSiteCleaned(_,_) = () @@ -103,20 +123,119 @@ type private ProjectSiteOfSingleFile(sourceFile) = override this.ProjectGuid = "" override this.LoadTime = new DateTime(2000,1,1) // any constant time is fine, orphan files do not interact with reloading based on update time override this.ProjectProvider = None - + override x.ToString() = sprintf "ProjectSiteOfSingleFile(%s)" sourceFile - + +/// Manage Storage of FSharpProjectOptions the options for a project +type internal FSharpProjectOptionsTable () = + // A table of information about projects, excluding single-file projects. + let projectTable = ConcurrentDictionary>() + let commandLineOptions = new ConcurrentDictionary() + + /// Go and re-get all of the options for everything that references projectId + let refreshInfoForProjectsThatReferenceThisProject (projectId:ProjectId) = + for KeyValue(otherProjectId, ((referencedProjectIds, _parsingOptions, _site, _options), refresh)) in projectTable.ToArray() do + for referencedProjectId in referencedProjectIds do + if referencedProjectId = projectId then + projectTable.[otherProjectId] <- (refresh true, refresh) + + /// Add or update a project in the project table + member __.AddOrUpdateProject(projectId:ProjectId, refresh) = + projectTable.[projectId] <- (refresh false, refresh) + refreshInfoForProjectsThatReferenceThisProject(projectId) + + /// Clear a project from the project table + member this.ClearInfoForProject(projectId:ProjectId) = + projectTable.TryRemove(projectId) |> ignore + refreshInfoForProjectsThatReferenceThisProject projectId + + /// Get the options for a project + member this.TryGetOptionsForProject(projectId:ProjectId) = + match projectTable.TryGetValue(projectId) with + | true, ((_referencedProjects, parsingOptions, site, projectOptions), _) -> Some (parsingOptions, site, projectOptions) + | _ -> None + + /// Given a projectId return the most recent set of command line options for it + member __.GetCommandLineOptionsWithProjectId(projectId:ProjectId) = + match commandLineOptions.TryGetValue projectId with + | true, (sources, references, options) -> sources, references, options + | _ -> [||], [||], [||] + + member this.SetOptionsWithProjectId(projectId:ProjectId, sourcePaths:string[], referencePaths:string[], options:string[]):bool = + match commandLineOptions.TryGetValue projectId with + | true, (existingSourcePaths, existingReferences, existingOptions) -> + not(Array.forall2(fun s1 s2 -> s1 = s2) sourcePaths existingSourcePaths || + Array.forall2(fun r1 r2 -> r1 = r2) referencePaths existingReferences || + Array.forall2(fun o1 o2 -> o1 = o2) options existingOptions) + | _ -> + commandLineOptions.[projectId] <- (sourcePaths, referencePaths, options) + true + +let internal provideProjectSiteProvider(workspace:VisualStudioWorkspaceImpl, project:Project, serviceProvider:System.IServiceProvider, projectOptionsTable:FSharpProjectOptionsTable option) = + let hier = workspace.GetHierarchy(project.Id) + let getCommandLineOptionsWithProjectId (projectId) = + match projectOptionsTable with + | Some (options) -> options.GetCommandLineOptionsWithProjectId(projectId) + | None -> [||], [||], [||] + {new IProvideProjectSite with + member x.GetProjectSite() = + let fst (a, _, _) = a + let snd (_, b, _) = b + let mutable errorReporter = + let reporter = ProjectExternalErrorReporter(project.Id, "FS", serviceProvider) + Some(reporter:> IVsLanguageServiceBuildErrorReporter2) + + { new IProjectSite with + member __.Description = project.Name + member __.CompilationSourceFiles = getCommandLineOptionsWithProjectId(project.Id) |> fst + member __.CompilationOptions = + let _,references,options = getCommandLineOptionsWithProjectId(project.Id) + Array.concat [options; references |> Array.map(fun r -> "-r:" + r)] + member __.CompilationReferences = getCommandLineOptionsWithProjectId(project.Id) |> snd + member site.CompilationBinOutputPath = site.CompilationOptions |> Array.tryPick (fun s -> if s.StartsWith("-o:") then Some s.[3..] else None) + member __.ProjectFileName = project.FilePath + member __.AdviseProjectSiteChanges(_,_) = () + member __.AdviseProjectSiteCleaned(_,_) = () + member __.AdviseProjectSiteClosed(_,_) = () + member __.IsIncompleteTypeCheckEnvironment = false + member __.TargetFrameworkMoniker = "" + member __.ProjectGuid = project.Id.Id.ToString() + member __.LoadTime = System.DateTime.Now + member __.ProjectProvider = Some (x) + member __.BuildErrorReporter with get () = errorReporter and + set (v) = errorReporter <- v + } + + interface IVsHierarchy with + member __.SetSite(psp) = hier.SetSite(psp) + member __.GetSite(psp) = hier.GetSite(ref psp) + member __.QueryClose(pfCanClose) = hier.QueryClose(ref pfCanClose) + member __.Close() = hier.Close() + member __.GetGuidProperty(itemid, propid, pguid) = hier.GetGuidProperty(itemid, propid, ref pguid) + member __.SetGuidProperty(itemid, propid, rguid) = hier.SetGuidProperty(itemid, propid, ref rguid) + member __.GetProperty(itemid, propid, pvar) = hier.GetProperty(itemid, propid, ref pvar) + member __.SetProperty(itemid, propid, var) = hier.SetProperty(itemid, propid, var) + member __.GetNestedHierarchy(itemid, iidHierarchyNested, ppHierarchyNested, pitemidNested) = hier.GetNestedHierarchy(itemid, ref iidHierarchyNested, ref ppHierarchyNested, ref pitemidNested) + member __.GetCanonicalName(itemid, pbstrName) = hier.GetCanonicalName(itemid, ref pbstrName) + member __.ParseCanonicalName(pszName, pitemid) = hier.ParseCanonicalName(pszName, ref pitemid) + member __.Unused0() = hier.Unused0() + member __.AdviseHierarchyEvents(pEventSink, pdwCookie) = hier.AdviseHierarchyEvents(pEventSink, ref pdwCookie) + member __.UnadviseHierarchyEvents(dwCookie) = hier.UnadviseHierarchyEvents(dwCookie) + member __.Unused1() = hier.Unused1() + member __.Unused2() = hier.Unused2() + member __.Unused3() = hier.Unused3() + member __.Unused4() = hier.Unused4() + } + /// Information about projects, open files and other active artifacts in visual studio. /// Keeps track of the relationship between IVsTextLines buffers, IFSharpSource_DEPRECATED objects, IProjectSite objects and FSharpProjectOptions [] type internal ProjectSitesAndFiles() = static let sourceUserDataGuid = new Guid("{55F834FD-B950-4C61-BBAA-0511ABAF4AE2}") // Guid for source user data on text buffer - static let mutable stamp = 0L static let tryGetProjectSite(hierarchy:IVsHierarchy) = match hierarchy with - | :? IProvideProjectSite as siteFactory -> - Some(siteFactory.GetProjectSite()) + | :? IProvideProjectSite as siteFactory -> Some(siteFactory.GetProjectSite()) | _ -> None static let fullOutputAssemblyPath (p:EnvDTE.Project) = @@ -132,64 +251,95 @@ type internal ProjectSitesAndFiles() = static let referencedProjects (projectSite:IProjectSite) = match projectSite.ProjectProvider with - | None -> Seq.empty - | Some (:? IVsHierarchy as hier) -> + | None -> None + | Some (:? IVsHierarchy as hier) -> match hier.GetProperty(VSConstants.VSITEMID_ROOT, int __VSHPROPID.VSHPROPID_ExtObject) with - | VSConstants.S_OK, (:? EnvDTE.Project as p) -> - (p.Object :?> VSLangProj.VSProject).References - |> Seq.cast - |> Seq.choose (fun r -> - Option.ofObj r - |> Option.bind (fun r -> try Option.ofObj r.SourceProject with _ -> None)) - | _ -> Seq.empty - | Some _ -> Seq.empty - - static let rec referencedProvideProjectSites (projectSite:IProjectSite, serviceProvider:System.IServiceProvider) = - seq { - let solutionService = try Some (serviceProvider.GetService(typeof) :?> IVsSolution) with _ -> None - match solutionService with - | Some solutionService -> - for p in referencedProjects projectSite do - match solutionService.GetProjectOfUniqueName(p.UniqueName) with - | VSConstants.S_OK, (:? IProvideProjectSite as ps) -> - yield (p, ps) - | _ -> () - | None -> () - } - - static let rec referencedProjectsOf (enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite:IProjectSite, extraProjectInfo, serviceProvider:System.IServiceProvider, useUniqueStamp) = - [| for (p,ps) in referencedProvideProjectSites (projectSite, serviceProvider) do - match fullOutputAssemblyPath p with + | VSConstants.S_OK, (:? EnvDTE.Project as p) when not (isNull p) -> + Some ((p.Object :?> VSLangProj.VSProject).References + |> Seq.cast + |> Seq.choose (fun r -> + Option.ofObj r + |> Option.bind (fun r -> try Option.ofObj r.SourceProject with _ -> None)) ) + | _ -> None + | Some _ -> None + + static let rec referencedProvideProjectSites(projectSite:IProjectSite, serviceProvider:System.IServiceProvider, extraProjectInfo:obj option, projectOptionsTable:FSharpProjectOptionsTable option) = + let getReferencesForSolutionService (solutionService:IVsSolution) = + [| + match referencedProjects projectSite, extraProjectInfo with + | None, Some (:? VisualStudioWorkspaceImpl as workspace) when not (isNull workspace.CurrentSolution)-> + let path = projectSite.ProjectFileName + if not (String.IsNullOrWhiteSpace(path)) then + let projectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath(path, projectDisplayNameOf path) + let project = workspace.CurrentSolution.GetProject(projectId) + if not (isNull project) then + for reference in project.ProjectReferences do + let project = workspace.CurrentSolution.GetProject(reference.ProjectId) + if not (isNull project) then + let siteProvider = provideProjectSiteProvider (workspace, project, serviceProvider, projectOptionsTable) + let referenceProject = workspace.ProjectTracker.GetProject(reference.ProjectId) + let outputPath = referenceProject.BinOutputPath + yield Some projectId, project.FilePath, outputPath, siteProvider + + | (Some references), _ -> + for p in references do + match solutionService.GetProjectOfUniqueName(p.UniqueName) with + | VSConstants.S_OK, (:? IProvideProjectSite as ps) -> + yield None, p.FileName, (fullOutputAssemblyPath p) |> Option.defaultValue "", ps + | _ -> () + | None, _ -> () + |] + let solutionService = try Some (serviceProvider.GetService(typeof) :?> IVsSolution) with _ -> None + seq { match solutionService with + | Some solutionService -> + for reference in getReferencesForSolutionService solutionService do yield reference | None -> () - | Some path -> - let referencedProjectOptions = - // Lookup may not succeed if the project has not been established yet - // In this case we go and compute the options recursively. - match tryGetOptionsForReferencedProject p.FileName with - | None -> getProjectOptionsForProjectSite (enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, ps.GetProjectSite(), p.FileName, extraProjectInfo, serviceProvider, useUniqueStamp) |> snd - | Some options -> options - yield (p.FileName, (path, referencedProjectOptions)) |] - - and getProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite:IProjectSite, fileName, extraProjectInfo, serviceProvider, useUniqueStamp) = + } + + static let rec referencedProjectsOf(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite, serviceProvider, extraProjectInfo, projectOptionsTable, useUniqueStamp) = + [| for (projectId, projectFileName, outputPath, projectSiteProvider) in referencedProvideProjectSites (projectSite, serviceProvider, extraProjectInfo, projectOptionsTable) do + let referencedProjectOptions = + // Lookup may not succeed if the project has not been established yet + // In this case we go and compute the options recursively. + match tryGetOptionsForReferencedProject projectFileName with + | None -> getProjectOptionsForProjectSite (enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSiteProvider.GetProjectSite(), serviceProvider, projectId, projectFileName, extraProjectInfo, projectOptionsTable, useUniqueStamp) |> snd + | Some options -> options + yield projectFileName, (outputPath, referencedProjectOptions) |] + + and getProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite, serviceProvider, projectId, fileName, extraProjectInfo, projectOptionsTable, useUniqueStamp) = let referencedProjectFileNames, referencedProjectOptions = if enableInMemoryCrossProjectReferences then - referencedProjectsOf(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite, extraProjectInfo, serviceProvider, useUniqueStamp) + referencedProjectsOf(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite, serviceProvider, extraProjectInfo, projectOptionsTable, useUniqueStamp) |> Array.unzip else [| |], [| |] - - let options = - {ProjectFileName = projectSite.ProjectFileName - SourceFiles = projectSite.CompilationSourceFiles - OtherOptions = projectSite.CompilationOptions - ReferencedProjects = referencedProjectOptions - IsIncompleteTypeCheckEnvironment = projectSite.IsIncompleteTypeCheckEnvironment - UseScriptResolutionRules = SourceFile.MustBeSingleFileProject fileName - LoadTime = projectSite.LoadTime - UnresolvedReferences = None - OriginalLoadReferences = [] - ExtraProjectInfo=extraProjectInfo - Stamp = (if useUniqueStamp then (stamp <- stamp + 1L; Some stamp) else None) } - referencedProjectFileNames, options + let option = + let newOption () = { + ProjectFileName = projectSite.ProjectFileName + SourceFiles = projectSite.CompilationSourceFiles + OtherOptions = projectSite.CompilationOptions + ReferencedProjects = referencedProjectOptions + IsIncompleteTypeCheckEnvironment = projectSite.IsIncompleteTypeCheckEnvironment + UseScriptResolutionRules = SourceFile.MustBeSingleFileProject fileName + LoadTime = projectSite.LoadTime + UnresolvedReferences = None + OriginalLoadReferences = [] + ExtraProjectInfo=extraProjectInfo + Stamp = if useUniqueStamp then (stamp <- stamp + 1L; Some stamp) else None + } + match projectId, projectOptionsTable with + | Some id, Some optionsTable -> + // Get options from cache + match optionsTable.TryGetOptionsForProject(id) with + | Some (_parsingOptions, _site, projectOptions) -> + if projectSite.CompilationSourceFiles <> projectOptions.SourceFiles || + projectSite.CompilationOptions <> projectOptions.OtherOptions || + referencedProjectOptions <> projectOptions.ReferencedProjects then + newOption() + else + projectOptions + | _ -> newOption() + | _ -> newOption() + referencedProjectFileNames, option /// Construct a project site for a single file. May be a single file project (for scripts) or an orphan project site (for everything else). static member ProjectSiteOfSingleFile(filename:string) : IProjectSite = @@ -198,9 +348,9 @@ type internal ProjectSitesAndFiles() = failwith ".fsx or .fsscript should have been treated as implicit project" new ProjectSiteOfSingleFile(filename) :> IProjectSite - static member GetReferencedProjectSites(projectSite:IProjectSite, serviceProvider:System.IServiceProvider) = - referencedProvideProjectSites (projectSite, serviceProvider) - |> Seq.map (fun (_, ps) -> ps.GetProjectSite()) + static member GetReferencedProjectSites(projectSite:IProjectSite, serviceProvider:System.IServiceProvider, extraProjectInfo, projectOptions) = + referencedProvideProjectSites (projectSite, serviceProvider, extraProjectInfo, projectOptions) + |> Seq.map (fun (_, _, _, ps) -> ps.GetProjectSite()) |> Seq.toArray member art.SetSource_DEPRECATED(buffer:IVsTextLines, source:IFSharpSource_DEPRECATED) : unit = @@ -208,11 +358,10 @@ type internal ProjectSitesAndFiles() = (buffer :?> IVsUserData).SetData(&guid, source) |> ErrorHandler.ThrowOnFailure |> ignore /// Create project options for this project site. - static member GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite:IProjectSite,filename,extraProjectInfo,serviceProvider:System.IServiceProvider, useUniqueStamp) = + static member GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite:IProjectSite, serviceProvider, projectId, filename, extraProjectInfo, projectOptionsTable, useUniqueStamp) = match projectSite with | :? IHaveCheckOptions as hco -> hco.OriginalCheckOptions() - | _ -> - getProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite, filename, extraProjectInfo, serviceProvider, useUniqueStamp) + | _ -> getProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite, serviceProvider, projectId, filename, extraProjectInfo, projectOptionsTable, useUniqueStamp) /// Create project site for these project options static member CreateProjectSiteForScript (filename, referencedProjectFileNames, checkOptions) = @@ -252,7 +401,6 @@ type internal ProjectSitesAndFiles() = let parsingOptions,_ = checker.GetParsingOptionsFromCommandLineArgs(site.CompilationOptions |> Array.toList) CompilerEnvironment.GetCompilationDefinesForEditing(filename,parsingOptions) - member art.TryFindOwningProject_DEPRECATED(rdt:IVsRunningDocumentTable, filename) = if SourceFile.MustBeSingleFileProject(filename) then None else @@ -271,4 +419,4 @@ type internal ProjectSitesAndFiles() = member art.FindOwningProject_DEPRECATED(rdt:IVsRunningDocumentTable, filename) = match art.TryFindOwningProject_DEPRECATED(rdt, filename) with | Some site -> site - | None -> ProjectSitesAndFiles.ProjectSiteOfSingleFile(filename) + | None -> ProjectSitesAndFiles.ProjectSiteOfSingleFile(filename) \ No newline at end of file diff --git a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs index 7a2976e0936..091908b3a04 100644 --- a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs +++ b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs @@ -17,6 +17,7 @@ open Microsoft.VisualStudio.OLE.Interop open Microsoft.FSharp.Compiler open Microsoft.FSharp.Compiler.SourceCodeServices open Microsoft.VisualStudio.FSharp.LanguageService +open Microsoft.VisualStudio.FSharp.LanguageService.SiteProvider open Microsoft.VisualStudio.FSharp.Editor type internal FSharpLanguageServiceTestable() as this = @@ -127,7 +128,7 @@ type internal FSharpLanguageServiceTestable() as this = /// Respond to project being cleaned/rebuilt (any live type providers in the project should be refreshed) member this.OnProjectCleaned(projectSite:IProjectSite) = let enableInMemoryCrossProjectReferences = true - let _, checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, (fun _ -> None), projectSite, "" ,None, serviceProvider.Value, false) + let _, checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, (fun _ -> None), projectSite, serviceProvider.Value, "" ,None, None, false) this.FSharpChecker.NotifyProjectCleaned(checkOptions) |> Async.RunSynchronously member this.OnActiveViewChanged(textView) = @@ -170,7 +171,7 @@ type internal FSharpLanguageServiceTestable() as this = member this.DependencyFileCreated projectSite = let enableInMemoryCrossProjectReferences = true // Invalidate the configuration if we notice any add for any DependencyFiles - let _, checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, (fun _ -> None), projectSite, "", None, this.ServiceProvider, false) + let _, checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, (fun _ -> None), projectSite, serviceProvider.Value, "" ,None, None, false) this.FSharpChecker.InvalidateConfiguration(checkOptions) member this.DependencyFileChanged (filename) = diff --git a/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj b/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj index 7ddbf1b0dbc..6a735d64415 100644 --- a/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj +++ b/vsintegration/tests/Salsa/VisualFSharp.Salsa.fsproj @@ -150,6 +150,10 @@ $(FSharpSourcesRoot)\..\packages\Microsoft.VisualStudio.Shell.$(RoslynVSBinariesVersion).$(RoslynVSPackagesVersion)\lib\Microsoft.VisualStudio.Shell.$(RoslynVSBinariesVersion).dll True + + $(FSharpSourcesRoot)\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.$(RoslynVersion)\lib\netstandard1.3\Microsoft.CodeAnalysis.Workspaces.dll + True + True $(NUnitLibDir)\nunit.framework.dll From d645905f40cbacbef061d1694c8101e1d531318a Mon Sep 17 00:00:00 2001 From: Kevin Ransom Date: Wed, 18 Oct 2017 17:56:13 -0700 Subject: [PATCH 2/6] Fix test --- vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs index 091908b3a04..fc02b71dada 100644 --- a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs +++ b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs @@ -128,7 +128,7 @@ type internal FSharpLanguageServiceTestable() as this = /// Respond to project being cleaned/rebuilt (any live type providers in the project should be refreshed) member this.OnProjectCleaned(projectSite:IProjectSite) = let enableInMemoryCrossProjectReferences = true - let _, checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, (fun _ -> None), projectSite, serviceProvider.Value, "" ,None, None, false) + let _, checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, (fun _ -> None), projectSite, serviceProvider.Value, None(*projectId*), "" ,None, None, false) this.FSharpChecker.NotifyProjectCleaned(checkOptions) |> Async.RunSynchronously member this.OnActiveViewChanged(textView) = From 44d5a22be2056c48f1b5e09b378727cf02c1b824 Mon Sep 17 00:00:00 2001 From: Kevin Ransom Date: Wed, 18 Oct 2017 21:26:27 -0700 Subject: [PATCH 3/6] test fix --- vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs index fc02b71dada..21b290c3819 100644 --- a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs +++ b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs @@ -171,7 +171,7 @@ type internal FSharpLanguageServiceTestable() as this = member this.DependencyFileCreated projectSite = let enableInMemoryCrossProjectReferences = true // Invalidate the configuration if we notice any add for any DependencyFiles - let _, checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, (fun _ -> None), projectSite, serviceProvider.Value, "" ,None, None, false) + let _, checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, (fun _ -> None), projectSite, serviceProvider.Value, None(*projectId*),"" ,None, None, false) this.FSharpChecker.InvalidateConfiguration(checkOptions) member this.DependencyFileChanged (filename) = From ec4ed8eb38180b34922cd76bc358aa779c8bde38 Mon Sep 17 00:00:00 2001 From: Kevin Ransom Date: Thu, 19 Oct 2017 22:44:47 -0700 Subject: [PATCH 4/6] go faster stripes --- src/fsharp/vs/service.fs | 2 +- .../LanguageService/LanguageService.fs | 66 ++++++++++++++----- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/fsharp/vs/service.fs b/src/fsharp/vs/service.fs index a2d354d8636..c5fd9cbc109 100644 --- a/src/fsharp/vs/service.fs +++ b/src/fsharp/vs/service.fs @@ -2722,7 +2722,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC member bc.InvalidateConfiguration(options : FSharpProjectOptions, startBackgroundCompileIfAlreadySeen, userOpName) = let startBackgroundCompileIfAlreadySeen = defaultArg startBackgroundCompileIfAlreadySeen implicitlyStartBackgroundWork // This operation can't currently be cancelled nor awaited - reactor.EnqueueOp(userOpName, "InvalidateConfiguration", options.ProjectFileName, fun ctok -> + reactor.EnqueueOp(userOpName, "InvalidateConfiguration: Stamp(" + (options.Stamp |> Option.defaultValue 0L).ToString() + ")", options.ProjectFileName, fun ctok -> // If there was a similar entry then re-establish an empty builder . This is a somewhat arbitrary choice - it // will have the effect of releasing memory associated with the previous builder, but costs some time. if incrementalBuildersCache.ContainsSimilarKey (ctok, options) then diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index 770af78a58d..94193e8bc1c 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -61,21 +61,21 @@ type internal FSharpCheckerProvider // When the F# background builder refreshes the background semantic build context for a file, // we request Roslyn to reanalyze that individual file. checker.BeforeBackgroundFileCheck.Add(fun (fileName, extraProjectInfo) -> - async { - try - match extraProjectInfo with - | Some (:? Workspace as workspace) -> - let solution = workspace.CurrentSolution - let documentIds = solution.GetDocumentIdsWithFilePath(fileName) - if not documentIds.IsEmpty then - let docuentIdsFiltered = documentIds |> Seq.filter workspace.IsDocumentOpen |> Seq.toArray - for documentId in docuentIdsFiltered do - Trace.TraceInformation("{0:n3} Requesting Roslyn reanalysis of {1}", DateTime.Now.TimeOfDay.TotalSeconds, documentId) - if docuentIdsFiltered.Length > 0 then - analyzerService.Reanalyze(workspace,documentIds=docuentIdsFiltered) - | _ -> () - with ex -> - Assert.Exception(ex) + async { + try + match extraProjectInfo with + | Some (:? Workspace as workspace) -> + let solution = workspace.CurrentSolution + let documentIds = solution.GetDocumentIdsWithFilePath(fileName) + if not documentIds.IsEmpty then + let documentIdsFiltered = documentIds |> Seq.filter workspace.IsDocumentOpen |> Seq.toArray + for documentId in documentIdsFiltered do + Trace.TraceInformation("{0:n3} Requesting Roslyn reanalysis of {1}", DateTime.Now.TimeOfDay.TotalSeconds, documentId) + if documentIdsFiltered.Length > 0 then + analyzerService.Reanalyze(workspace,documentIds=documentIdsFiltered) + | _ -> () + with ex -> + Assert.Exception(ex) } |> Async.StartImmediate ) checker @@ -214,6 +214,21 @@ type internal FSharpProjectOptionsManager this.UpdateProjectInfo(tryGetOrCreateProjectId, projectId, siteProvider.GetProjectSite(), userOpName) | _ -> () + /// Tell the checker to update the project info for the specified project id + member this.UpdateDocumenttInfoWithProjectId(projectId:ProjectId, documentId:DocumentId, userOpName) = + let hier = workspace.GetHierarchy(projectId) + match hier with + | null -> () + | h when (h.IsCapabilityMatch("CPS")) -> + if workspace.IsDocumentOpen(documentId) then + if not (isNull workspace.CurrentSolution) then + let project = workspace.CurrentSolution.GetProject(projectId) + if not (isNull project) then + let siteProvider = provideProjectSiteProvider(workspace, project, serviceProvider, Some projectOptionsTable) + this.UpdateProjectInfo(tryGetOrCreateProjectId, projectId, siteProvider.GetProjectSite(), userOpName) + | _ -> () + + [] /// This handles commandline change notifications from the Dotnet Project-system member this.HandleCommandLineChanges(path:string, sources:ImmutableArray, references:ImmutableArray, options:ImmutableArray) = @@ -339,8 +354,10 @@ type let optionsAssociation = ConditionalWeakTable() member private this.OnProjectAdded(projectId:ProjectId) = projectInfoManager.UpdateProjectInfoWithProjectId(projectId, "OnProjectAdded") - member private this.OnProjectChanged(projectId:ProjectId) = projectInfoManager.UpdateProjectInfoWithProjectId(projectId, "OnProjectChanged") member private this.OnProjectReloaded(projectId:ProjectId) = projectInfoManager.UpdateProjectInfoWithProjectId(projectId, "OnProjectReloaded") + member private this.OnDocumentAdded(projectId:ProjectId, documentId:DocumentId) = projectInfoManager.UpdateDocumenttInfoWithProjectId(projectId, documentId, "OnDocumentAdded") + member private this.OnDocumentChanged(projectId:ProjectId, documentId:DocumentId) = projectInfoManager.UpdateDocumenttInfoWithProjectId(projectId, documentId, "OnDocumentChanged") + member private this.OnDocumentReloaded(projectId:ProjectId, documentId:DocumentId) = projectInfoManager.UpdateDocumenttInfoWithProjectId(projectId, documentId, "OnDocumentReloaded") override this.Initialize() = base.Initialize() @@ -348,8 +365,23 @@ type let workspaceChanged (args:WorkspaceChangeEventArgs) = match args.Kind with | WorkspaceChangeKind.ProjectAdded -> this.OnProjectAdded(args.ProjectId) - | WorkspaceChangeKind.ProjectChanged -> this.OnProjectChanged(args.ProjectId) | WorkspaceChangeKind.ProjectReloaded -> this.OnProjectReloaded(args.ProjectId) + | WorkspaceChangeKind.DocumentAdded -> this.OnDocumentAdded(args.ProjectId, args.DocumentId) + | WorkspaceChangeKind.DocumentRemoved -> this.OnDocumentAdded(args.ProjectId, args.DocumentId) + | WorkspaceChangeKind.ProjectRemoved + | WorkspaceChangeKind.DocumentAdded + | WorkspaceChangeKind.DocumentReloaded + | WorkspaceChangeKind.DocumentRemoved + | WorkspaceChangeKind.DocumentInfoChanged + | WorkspaceChangeKind.DocumentChanged + | WorkspaceChangeKind.AdditionalDocumentAdded + | WorkspaceChangeKind.AdditionalDocumentReloaded + | WorkspaceChangeKind.AdditionalDocumentRemoved + | WorkspaceChangeKind.AdditionalDocumentChanged + | WorkspaceChangeKind.SolutionAdded + | WorkspaceChangeKind.SolutionChanged + | WorkspaceChangeKind.SolutionReloaded + | WorkspaceChangeKind.SolutionCleared | _ -> () this.Workspace.Options <- this.Workspace.Options.WithChangedOption(Completion.CompletionOptions.BlockForCompletionItems, FSharpConstants.FSharpLanguageName, false) From f9af0ce413a95df3bfb17c433de88e6da72525b2 Mon Sep 17 00:00:00 2001 From: Kevin Ransom Date: Sat, 21 Oct 2017 23:02:12 -0700 Subject: [PATCH 5/6] new project works better --- .../LanguageService/LanguageService.fs | 39 ++++++++++--------- .../ProjectSitesAndFiles.fs | 11 +----- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index 94193e8bc1c..b6bc82e58a5 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -143,20 +143,20 @@ type internal FSharpProjectOptionsManager } /// Update the info for a project in the project table - member this.UpdateProjectInfo(tryGetOrCreateProjectId, projectId: ProjectId, site:IProjectSite, userOpName) = + member this.UpdateProjectInfo(tryGetOrCreateProjectId, projectId, site, userOpName) = projectOptionsTable.AddOrUpdateProject(projectId, (fun isRefresh -> let extraProjectInfo = Some(box workspace) let tryGetOptionsForReferencedProject f = f |> tryGetOrCreateProjectId |> Option.bind this.TryGetOptionsForProject |> Option.map(fun (_, _, projectOptions) -> projectOptions) let referencedProjects, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, serviceProvider, (tryGetOrCreateProjectId (site.ProjectFileName)), site.ProjectFileName, extraProjectInfo, Some projectOptionsTable, true) - let referencedProjectIds = referencedProjects |> Array.choose tryGetOrCreateProjectId checkerProvider.Checker.InvalidateConfiguration(projectOptions, startBackgroundCompileIfAlreadySeen = not isRefresh, userOpName = userOpName + ".UpdateProjectInfo") + let referencedProjectIds = referencedProjects |> Array.choose tryGetOrCreateProjectId let parsingOptions, _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions(projectOptions) referencedProjectIds, parsingOptions, Some site, projectOptions)) /// Get compilation defines relevant for syntax processing. /// Quicker then TryGetOptionsForDocumentOrProject as it doesn't need to recompute the exact project /// options for a script. - member this.GetCompilationDefinesForEditingDocument(document: Document) = + member this.GetCompilationDefinesForEditingDocument(document:Document) = let projectOptionsOpt = this.TryGetOptionsForProject(document.Project.Id) let parsingOptions = match projectOptionsOpt with @@ -193,7 +193,7 @@ type internal FSharpProjectOptionsManager /// Get the options for a document or project relevant for syntax processing. /// Quicker then TryGetOptionsForDocumentOrProject as it doesn't need to recompute the exact project options for a script. - member this.TryGetOptionsForEditingDocumentOrProject(document: Document) = + member this.TryGetOptionsForEditingDocumentOrProject(document:Document) = let projectId = document.Project.Id match singleFileProjectTable.TryGetValue(projectId) with | true, (_loadTime, parsingOptions, originalOptions) -> Some (parsingOptions, originalOptions) @@ -211,7 +211,9 @@ type internal FSharpProjectOptionsManager let project = workspace.CurrentSolution.GetProject(projectId) if not (isNull project) then let siteProvider = provideProjectSiteProvider(workspace, project, serviceProvider, Some projectOptionsTable) - this.UpdateProjectInfo(tryGetOrCreateProjectId, projectId, siteProvider.GetProjectSite(), userOpName) + let projectSite = siteProvider.GetProjectSite() + if projectSite.CompilationSourceFiles.Length <> 0 then + this.UpdateProjectInfo(tryGetOrCreateProjectId, projectId, projectSite, userOpName) | _ -> () /// Tell the checker to update the project info for the specified project id @@ -238,8 +240,8 @@ type internal FSharpProjectOptionsManager let sourcePaths = sources |> Seq.map(fun s -> fullPath s.Path) |> Seq.toArray let referencePaths = references |> Seq.map(fun r -> fullPath r.Reference) |> Seq.toArray let projectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath(path, projectDisplayNameOf path) - if projectOptionsTable.SetOptionsWithProjectId(projectId, sourcePaths,referencePaths,options.ToArray()) then - this.UpdateProjectInfoWithProjectId(projectId, "HandleCommandLineChanges") + projectOptionsTable.SetOptionsWithProjectId(projectId, sourcePaths, referencePaths, options.ToArray()) + this.UpdateProjectInfoWithProjectId(projectId, "HandleCommandLineChanges") member __.Checker = checkerProvider.Checker @@ -353,10 +355,10 @@ type let optionsAssociation = ConditionalWeakTable() - member private this.OnProjectAdded(projectId:ProjectId) = projectInfoManager.UpdateProjectInfoWithProjectId(projectId, "OnProjectAdded") + member private this.OnProjectAdded(projectId:ProjectId) = projectInfoManager.UpdateProjectInfoWithProjectId(projectId, "OnProjectAdded") member private this.OnProjectReloaded(projectId:ProjectId) = projectInfoManager.UpdateProjectInfoWithProjectId(projectId, "OnProjectReloaded") - member private this.OnDocumentAdded(projectId:ProjectId, documentId:DocumentId) = projectInfoManager.UpdateDocumenttInfoWithProjectId(projectId, documentId, "OnDocumentAdded") - member private this.OnDocumentChanged(projectId:ProjectId, documentId:DocumentId) = projectInfoManager.UpdateDocumenttInfoWithProjectId(projectId, documentId, "OnDocumentChanged") + member private this.OnDocumentAdded(projectId:ProjectId, documentId:DocumentId) = projectInfoManager.UpdateDocumenttInfoWithProjectId(projectId, documentId, "OnDocumentAdded") + member private this.OnDocumentChanged(projectId:ProjectId, documentId:DocumentId) = projectInfoManager.UpdateDocumenttInfoWithProjectId(projectId, documentId, "OnDocumentChanged") member private this.OnDocumentReloaded(projectId:ProjectId, documentId:DocumentId) = projectInfoManager.UpdateDocumenttInfoWithProjectId(projectId, documentId, "OnDocumentReloaded") override this.Initialize() = @@ -364,20 +366,19 @@ type let workspaceChanged (args:WorkspaceChangeEventArgs) = match args.Kind with - | WorkspaceChangeKind.ProjectAdded -> this.OnProjectAdded(args.ProjectId) - | WorkspaceChangeKind.ProjectReloaded -> this.OnProjectReloaded(args.ProjectId) - | WorkspaceChangeKind.DocumentAdded -> this.OnDocumentAdded(args.ProjectId, args.DocumentId) - | WorkspaceChangeKind.DocumentRemoved -> this.OnDocumentAdded(args.ProjectId, args.DocumentId) - | WorkspaceChangeKind.ProjectRemoved - | WorkspaceChangeKind.DocumentAdded - | WorkspaceChangeKind.DocumentReloaded + | WorkspaceChangeKind.ProjectAdded -> this.OnProjectAdded(args.ProjectId) + | WorkspaceChangeKind.ProjectReloaded -> this.OnProjectReloaded(args.ProjectId) + | WorkspaceChangeKind.DocumentAdded -> this.OnDocumentAdded(args.ProjectId, args.DocumentId) + | WorkspaceChangeKind.DocumentChanged -> this.OnDocumentChanged(args.ProjectId, args.DocumentId) + | WorkspaceChangeKind.DocumentReloaded -> this.OnDocumentReloaded(args.ProjectId, args.DocumentId) | WorkspaceChangeKind.DocumentRemoved - | WorkspaceChangeKind.DocumentInfoChanged - | WorkspaceChangeKind.DocumentChanged + | WorkspaceChangeKind.ProjectRemoved | WorkspaceChangeKind.AdditionalDocumentAdded | WorkspaceChangeKind.AdditionalDocumentReloaded | WorkspaceChangeKind.AdditionalDocumentRemoved | WorkspaceChangeKind.AdditionalDocumentChanged + | WorkspaceChangeKind.DocumentInfoChanged + | WorkspaceChangeKind.DocumentChanged | WorkspaceChangeKind.SolutionAdded | WorkspaceChangeKind.SolutionChanged | WorkspaceChangeKind.SolutionReloaded diff --git a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs index e037eb79fe2..7f706e98443 100644 --- a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs +++ b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs @@ -161,15 +161,8 @@ type internal FSharpProjectOptionsTable () = | true, (sources, references, options) -> sources, references, options | _ -> [||], [||], [||] - member this.SetOptionsWithProjectId(projectId:ProjectId, sourcePaths:string[], referencePaths:string[], options:string[]):bool = - match commandLineOptions.TryGetValue projectId with - | true, (existingSourcePaths, existingReferences, existingOptions) -> - not(Array.forall2(fun s1 s2 -> s1 = s2) sourcePaths existingSourcePaths || - Array.forall2(fun r1 r2 -> r1 = r2) referencePaths existingReferences || - Array.forall2(fun o1 o2 -> o1 = o2) options existingOptions) - | _ -> - commandLineOptions.[projectId] <- (sourcePaths, referencePaths, options) - true + member this.SetOptionsWithProjectId(projectId:ProjectId, sourcePaths:string[], referencePaths:string[], options:string[]) = + commandLineOptions.[projectId] <- (sourcePaths, referencePaths, options) let internal provideProjectSiteProvider(workspace:VisualStudioWorkspaceImpl, project:Project, serviceProvider:System.IServiceProvider, projectOptionsTable:FSharpProjectOptionsTable option) = let hier = workspace.GetHierarchy(project.Id) From 8f7c475e25719e9d0778b1b34bb5ed13505e0290 Mon Sep 17 00:00:00 2001 From: Kevin Ransom Date: Mon, 23 Oct 2017 16:07:04 -0700 Subject: [PATCH 6/6] Re-add debug assert for sourcefiles --- src/fsharp/vs/service.fs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fsharp/vs/service.fs b/src/fsharp/vs/service.fs index c5fd9cbc109..3d86ffa63cf 100644 --- a/src/fsharp/vs/service.fs +++ b/src/fsharp/vs/service.fs @@ -1374,8 +1374,8 @@ type FSharpParsingOptions = } member x.LastFileName = - if x.SourceFiles.Length = 0 then "" - else Array.last x.SourceFiles + Debug.Assert(not (Array.isEmpty x.SourceFiles), "Parsing options don't contain any file") + Array.last x.SourceFiles static member Default = { SourceFiles = Array.empty @@ -1397,7 +1397,7 @@ type FSharpParsingOptions = } static member FromTcConfigBuidler(tcConfigB: TcConfigBuilder, sourceFiles) = - { + { SourceFiles = sourceFiles ConditionalCompilationDefines = tcConfigB.conditionalCompilationDefines ErrorSeverityOptions = tcConfigB.errorSeverityOptions