diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index 8cf6d94af9d..8a9ad0170c4 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -149,7 +149,6 @@ type internal FSharpProjectOptionsManager member this.ComputeSingleFileOptions (tryGetOrCreateProjectId, fileName, loadTime, fileContents) = async { let extraProjectInfo = Some(box workspace) - 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. @@ -159,12 +158,12 @@ 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, serviceProvider, (tryGetOrCreateProjectId fileName), fileName, options.ExtraProjectInfo, Some projectOptionsTable) + let deps, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, site, serviceProvider, (tryGetOrCreateProjectId fileName), fileName, options.ExtraProjectInfo, Some projectOptionsTable) 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, serviceProvider, (tryGetOrCreateProjectId fileName), fileName, extraProjectInfo, Some projectOptionsTable) + let deps, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, site, serviceProvider, (tryGetOrCreateProjectId fileName), fileName, extraProjectInfo, Some projectOptionsTable) let parsingOptions, _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions(projectOptions) return (deps, parsingOptions, projectOptions) } @@ -174,8 +173,7 @@ type internal FSharpProjectOptionsManager Logger.Log LogEditorFunctionId.LanguageService_UpdateProjectInfo 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, Some(projectId), site.ProjectFileName, extraProjectInfo, Some projectOptionsTable) + let referencedProjects, projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, site, serviceProvider, Some(projectId), site.ProjectFileName, extraProjectInfo, Some projectOptionsTable) if invalidateConfig then checkerProvider.Checker.InvalidateConfiguration(projectOptions, startBackgroundCompileIfAlreadySeen = not isRefresh, userOpName = userOpName + ".UpdateProjectInfo") let referencedProjectIds = referencedProjects |> Array.choose tryGetOrCreateProjectId let parsingOptions, _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions(projectOptions) @@ -610,7 +608,7 @@ type internal FSharpLanguageService(package : FSharpPackage) = optionsAssociation.Remove(projectContext) |> ignore project.Disconnect())) - for referencedSite in ProjectSitesAndFiles.GetReferencedProjectSites(site, this.SystemServiceProvider, Some (this.Workspace :>obj), Some projectInfoManager.FSharpOptions ) do + for referencedSite in ProjectSitesAndFiles.GetReferencedProjectSites(Some projectId, site, this.SystemServiceProvider, Some (this.Workspace :>obj), Some projectInfoManager.FSharpOptions ) do setup referencedSite setup (siteProvider.GetProjectSite()) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/ProjectSitesAndFiles.fs b/vsintegration/src/FSharp.Editor/LanguageService/ProjectSitesAndFiles.fs index 7697de18904..1bea848dae2 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/ProjectSitesAndFiles.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/ProjectSitesAndFiles.fs @@ -221,23 +221,25 @@ type internal ProjectSitesAndFiles() = | _ -> None | Some _ -> None - static let rec referencedProvideProjectSites(projectSite:IProjectSite, serviceProvider:System.IServiceProvider, extraProjectInfo:obj option, projectOptionsTable:FSharpProjectOptionsTable option) = + static let rec referencedProvideProjectSites(projectIdOpt: ProjectId option, 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) && project.Language = FSharpConstants.FSharpLanguageName 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 + match projectIdOpt with + | Some(projectId) -> + 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) && project.Language = FSharpConstants.FSharpLanguageName then + let siteProvider = provideProjectSiteProvider (workspace, project, serviceProvider, projectOptionsTable) + let referenceProject = workspace.ProjectTracker.GetProject(reference.ProjectId) + let outputPath = referenceProject.BinOutputPath + yield Some project.Id, project.FilePath, outputPath, siteProvider + | _ -> () | (Some references), _ -> for p in references do @@ -255,26 +257,33 @@ type internal ProjectSitesAndFiles() = | None -> () } - static let rec referencedProjectsOf(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite, serviceProvider, extraProjectInfo, projectOptionsTable) = - [| 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) |> snd - | Some options -> options - yield projectFileName, (outputPath, referencedProjectOptions) |] - - and getProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite, serviceProvider, projectId, fileName, extraProjectInfo, projectOptionsTable) = + static let rec referencedProjectsOf(projectIdOpt, projectSite, serviceProvider, extraProjectInfo, projectOptionsTable) = + [| for (projectIdOpt, projectFileName, outputPath, _projectSiteProvider) in referencedProvideProjectSites (projectIdOpt, projectSite, serviceProvider, extraProjectInfo, projectOptionsTable) do + let referencedProjectOptionsOpt = + projectOptionsTable + |> Option.bind (fun x -> + match projectIdOpt with + | Some(projectId) -> x.TryGetOptionsForProject(projectId) + | _ -> None + ) + |> Option.map (fun (_, _, options) -> options) + + match referencedProjectOptionsOpt with + | Some(referencedProjectOptions) -> + yield projectFileName, (outputPath, referencedProjectOptions) + | _ -> () + |] + + and getProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, projectSite, serviceProvider, projectIdOpt, fileName, extraProjectInfo, projectOptionsTable) = let referencedProjectFileNames, referencedProjectOptions = if enableInMemoryCrossProjectReferences then - referencedProjectsOf(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite, serviceProvider, extraProjectInfo, projectOptionsTable) + referencedProjectsOf(projectIdOpt, projectSite, serviceProvider, extraProjectInfo, projectOptionsTable) |> Array.unzip else [| |], [| |] let option = let newOption () = { ProjectFileName = projectSite.ProjectFileName - ProjectId = projectId |> Option.map (fun x -> x.ToFSharpProjectIdString()) + ProjectId = projectIdOpt |> Option.map (fun x -> x.ToFSharpProjectIdString()) SourceFiles = projectSite.CompilationSourceFiles OtherOptions = projectSite.CompilationOptions ReferencedProjects = referencedProjectOptions @@ -286,7 +295,7 @@ type internal ProjectSitesAndFiles() = ExtraProjectInfo=extraProjectInfo Stamp = (stamp <- stamp + 1L; Some stamp) } - match projectId, projectOptionsTable with + match projectIdOpt, projectOptionsTable with | Some id, Some optionsTable -> // Get options from cache match optionsTable.TryGetOptionsForProject(id) with @@ -308,16 +317,16 @@ 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, extraProjectInfo, projectOptions) = - referencedProvideProjectSites (projectSite, serviceProvider, extraProjectInfo, projectOptions) + static member GetReferencedProjectSites(projectIdOpt, projectSite:IProjectSite, serviceProvider:System.IServiceProvider, extraProjectInfo, projectOptions) = + referencedProvideProjectSites (projectIdOpt, projectSite, serviceProvider, extraProjectInfo, projectOptions) |> Seq.map (fun (_, _, _, ps) -> ps.GetProjectSite()) |> Seq.toArray /// Create project options for this project site. - static member GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite:IProjectSite, serviceProvider, projectId, filename, extraProjectInfo, projectOptionsTable) = + static member GetProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, projectSite:IProjectSite, serviceProvider, projectId, filename, extraProjectInfo, projectOptionsTable) = match projectSite with | :? IHaveCheckOptions as hco -> hco.OriginalCheckOptions() - | _ -> getProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, projectSite, serviceProvider, projectId, filename, extraProjectInfo, projectOptionsTable) + | _ -> getProjectOptionsForProjectSite(enableInMemoryCrossProjectReferences, projectSite, serviceProvider, projectId, filename, extraProjectInfo, projectOptionsTable) /// Create project site for these project options static member CreateProjectSiteForScript (filename, referencedProjectFileNames, checkOptions) =