Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 123 additions & 54 deletions src/fsharp/service/service.fs

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion src/fsharp/service/service.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ type public FSharpChecker =
/// <param name="tryGetMetadataSnapshot">An optional resolver to access the contents of .NET binaries in a memory-efficient way</param>
static member Create : ?projectCacheSize: int * ?keepAssemblyContents: bool * ?keepAllBackgroundResolutions: bool * ?legacyReferenceResolver: ReferenceResolver.Resolver * ?tryGetMetadataSnapshot: ILReaderTryGetMetadataSnapshot * ?suggestNamesForErrors: bool -> FSharpChecker

/// For testing purposes, not for public consumption.
/// Tries to find if the given project exists in any kind of cache, whether it be incremental builder, check file, check file stale cache.
member internal ProjectExistsInAnyCache: options: FSharpProjectOptions * ?userOpName: string -> Async<bool>

/// <summary>
/// Parse a source code file, returning information about brace matching in the file.
/// Return an enumeration of the matching parenthetical tokens in the file.
Expand Down Expand Up @@ -633,7 +637,12 @@ type public FSharpChecker =
/// For example, dependent references may have been deleted or created.
/// <param name="startBackgroundCompileIfAlreadySeen">Start a background compile of the project if a project with the same name has already been seen before.</param>
/// <param name="userOpName">An optional string used for tracing compiler operations associated with this request.</param>
member InvalidateConfiguration: options: FSharpProjectOptions * ?startBackgroundCompileIfAlreadySeen: bool * ?userOpName: string -> unit
member InvalidateConfiguration: options: FSharpProjectOptions * ?startBackgroundCompileIfAlreadySeen: bool * ?userOpName: string -> unit

/// Invalidate a project. Clears all caches related to this the project represented by the given FSharpProjectOptions, including check files, stale check files, etc.
/// <param name="options">The options for the project or script, used to determine active --define conditionals and other options relevant to parsing.</param>
/// <param name="userOpName">An optional string used for tracing compiler operations associated with this request.</param>
member InvalidateProject: options: FSharpProjectOptions * ?userOpName: string -> unit

/// Set the project to be checked in the background. Overrides any previous call to <c>CheckProjectInBackground</c>
member CheckProjectInBackground: options: FSharpProjectOptions * ?userOpName: string -> unit
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,15 @@ type internal FSharpCheckerWorkspaceServiceFactory
member this.CreateService(_workspaceServices) =
upcast { new FSharpCheckerWorkspaceService with
member this.Checker = checkerProvider.Checker
member this.FSharpProjectOptionsManager = projectInfoManager }
member this.FSharpProjectOptionsManager = projectInfoManager }

[<Sealed>]
type private FSharpSolutionEvents(projectManager: FSharpProjectOptionsManager) =

interface IVsSolutionEvents with

member __.OnAfterCloseSolution(_) =
projectManager.Checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients()
projectManager.Reset ()
VSConstants.S_OK

member __.OnAfterLoadProject(_, _) = VSConstants.E_NOTIMPL
Expand All @@ -98,7 +98,7 @@ type private FSharpSolutionEvents(projectManager: FSharpProjectOptionsManager) =

member __.OnQueryCloseSolution(_, _) = VSConstants.E_NOTIMPL

member __.OnQueryUnloadProject(_, _) = VSConstants.E_NOTIMPL
member __.OnQueryUnloadProject(_, _) = VSConstants.E_NOTIMPL

[<Microsoft.CodeAnalysis.Host.Mef.ExportWorkspaceServiceFactory(typeof<EditorOptions>, Microsoft.CodeAnalysis.Host.Mef.ServiceLayer.Default)>]
type internal FSharpSettingsFactory
Expand Down Expand Up @@ -170,7 +170,7 @@ type internal FSharpPackage() as this =
vfsiToolWindow <- this.FindToolWindow(typeof<Microsoft.VisualStudio.FSharp.Interactive.FsiToolWindow>, 0, true) :?> Microsoft.VisualStudio.FSharp.Interactive.FsiToolWindow
vfsiToolWindow :> Microsoft.VisualStudio.FSharp.Interactive.ITestVFSI

let mutable solutionEventsOpt = None
let mutable solutionEventsOpt = None // this is meant to strongly hold onto solution events

// FSI-LINKAGE-POINT: unsited init
do
Expand Down Expand Up @@ -204,8 +204,8 @@ type internal FSharpPackage() as this =
let projectContextFactory = this.ComponentModel.GetService<IWorkspaceProjectContextFactory>()
let workspace = this.ComponentModel.GetService<VisualStudioWorkspace>()
let miscFilesWorkspace = this.ComponentModel.GetService<MiscellaneousFilesWorkspace>()
let _singleFileWorkspaceMap = new SingleFileWorkspaceMap(workspace, miscFilesWorkspace, projectInfoManager, projectContextFactory, rdt)
let _legacyProjectWorkspaceMap = new LegacyProjectWorkspaceMap(solution, projectInfoManager, projectContextFactory)
let _singleFileWorkspaceMap = new SingleFileWorkspaceMap(workspace, miscFilesWorkspace, projectContextFactory, rdt)
let _legacyProjectWorkspaceMap = new LegacyProjectWorkspaceMap(solution, projectContextFactory)
()
let awaiter = this.JoinableTaskFactory.SwitchToMainThreadAsync().GetAwaiter()
if awaiter.IsCompleted then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ open Microsoft.VisualStudio.LanguageServices.ProjectSystem
open Microsoft.VisualStudio.Shell.Interop

[<Sealed>]
type internal LegacyProjectWorkspaceMap(solution: IVsSolution,
projectInfoManager: FSharpProjectOptionsManager,
type internal LegacyProjectWorkspaceMap(solution: IVsSolution,
projectContextFactory: IWorkspaceProjectContextFactory) as this =

let invalidPathChars = set (Path.GetInvalidPathChars())
Expand Down Expand Up @@ -136,8 +135,7 @@ type internal LegacyProjectWorkspaceMap(solution: IVsSolution,
AdviseProjectSiteChanges(fun () -> this.SyncLegacyProject(projectContext, site)))

site.AdviseProjectSiteClosed(FSharpConstants.FSharpLanguageServiceCallbackName,
AdviseProjectSiteChanges(fun () ->
projectInfoManager.ClearInfoForProject(projectContext.Id)
AdviseProjectSiteChanges(fun () ->
optionsAssociation.Remove(projectContext) |> ignore
projectContext.Dispose()))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ open Microsoft.VisualStudio.LanguageServices
[<Sealed>]
type internal SingleFileWorkspaceMap(workspace: VisualStudioWorkspace,
miscFilesWorkspace: MiscellaneousFilesWorkspace,
optionsManager: FSharpProjectOptionsManager,
projectContextFactory: IWorkspaceProjectContextFactory,
rdt: IVsRunningDocumentTable) as this =

Expand Down Expand Up @@ -45,7 +44,6 @@ type internal SingleFileWorkspaceMap(workspace: VisualStudioWorkspace,
if document.Project.Language = FSharpConstants.FSharpLanguageName && document.Project.Name <> FSharpConstants.FSharpMiscellaneousFilesName then
match files.TryRemove(document.FilePath) with
| true, projectContext ->
optionsManager.ClearSingleFileOptionsCache(document.Id)
projectContext.Dispose()
| _ -> ()
)
Expand All @@ -54,7 +52,6 @@ type internal SingleFileWorkspaceMap(workspace: VisualStudioWorkspace,
let document = args.Document
match files.TryRemove(document.FilePath) with
| true, projectContext ->
optionsManager.ClearSingleFileOptionsCache(document.Id)
projectContext.Dispose()
| _ -> ()
)
Expand Down Expand Up @@ -84,20 +81,9 @@ type internal SingleFileWorkspaceMap(workspace: VisualStudioWorkspace,
// Handles renaming of a misc file
if (grfAttribs &&& (uint32 __VSRDTATTRIB.RDTA_MkDocument)) <> 0u && files.ContainsKey(pszMkDocumentOld) then
match files.TryRemove(pszMkDocumentOld) with
| true, projectContext ->
let project = workspace.CurrentSolution.GetProject(projectContext.Id)
if project <> null then
let documentOpt =
project.Documents
|> Seq.tryFind (fun x -> String.Equals(x.FilePath, pszMkDocumentOld, StringComparison.OrdinalIgnoreCase))
match documentOpt with
| None -> ()
| Some(document) ->
optionsManager.ClearSingleFileOptionsCache(document.Id)
projectContext.Dispose()
files.[pszMkDocumentNew] <- createProjectContext pszMkDocumentNew
else
projectContext.Dispose() // fallback, shouldn't happen, but in case it does let's dispose of the project context so we don't leak
| true, projectContext ->
projectContext.Dispose()
files.[pszMkDocumentNew] <- createProjectContext pszMkDocumentNew
| _ -> ()
VSConstants.S_OK

Expand Down
149 changes: 149 additions & 0 deletions vsintegration/tests/UnitTests/CacheTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace Microsoft.VisualStudio.FSharp.Editor.Tests.Roslyn

open System.IO
open System.Threading
open NUnit.Framework
open Microsoft.VisualStudio.FSharp.Editor
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Text
open UnitTests.TestLib.LanguageService

[<TestFixture; Category "Roslyn and FCS Integration"; NonParallelizable>]
type CacheTests() =

let testPath = """C:\test\"""
let testFileName = "TestFile.fs"
let testFilePath = Path.Combine(testPath, testFileName)
let testFileSourceText = SourceText.From("""module TestProject.TestFile""")

let createTestProjectInfo name =
let projectId = ProjectId.CreateNewId ()
let documentInfos =
[
DocumentInfo.Create (DocumentId.CreateNewId (projectId), testFileName)
]
let dllPath = Path.Combine(testPath, name + ".dll")
let projectPath = Path.Combine(testPath, name + ".fsproj")
ProjectInfo.Create (projectId, VersionStamp.Create (), name, name, (* Can't use "F#" due to exception *) "C#", documents = documentInfos, outputFilePath = dllPath, filePath = projectPath)

let createSolutionInfoWithProjectCount projectCount =
let testProjectInfos = [ for i = 1 to projectCount do yield createTestProjectInfo ("Test" + string i) ]
let solutionInfo = SolutionInfo.Create(SolutionId.CreateNewId (), VersionStamp.Create (), projects = testProjectInfos)
solutionInfo

let createCompilationEnvWithProjectCount projectCount =
let solutionInfo = createSolutionInfoWithProjectCount projectCount
let projectInfos = solutionInfo.Projects
let workspace = new AdhocWorkspace()
workspace.AddSolution solutionInfo |> ignore

let cenv = FSharpCompilationHelpers.createCompilationEnv workspace checker (* enableInMemoryCrossProjectReferences *) true

// This will go away when HandleCommandLineChanges goes away.
projectInfos
|> Seq.iter (fun testProjectInfo ->
cenv.cpsCommandLineOptions.[testProjectInfo.Id] <- ([|testFilePath|], [||])
)

(workspace, cenv)

let basicTest () =
let (workspace, cenv) = createCompilationEnvWithProjectCount 3 (* we chose 3 because the Checker's default project cache size is 3 by default *)
let checker = cenv.checker

checker.StopBackgroundCompile ()
checker.InvalidateAll ()

// We shouldn't have a current solution yet.
Assert.True (cenv.currentSolution.IsNone)

let fsharpProjects =
workspace.CurrentSolution.Projects
|> Seq.map (fun project ->
FSharpCompilationHelpers.tryComputeOptions cenv project CancellationToken.None |> Async.RunSynchronously
)
|> List.ofSeq

// We still don't have a current solution yet.
Assert.True (cenv.currentSolution.IsNone)

FSharpCompilationHelpers.setSolution cenv workspace.CurrentSolution

// We should now have a current solution.
Assert.True (cenv.currentSolution.IsSome)

Assert.True (fsharpProjects |> List.forall (fun x -> x.IsSome))

let fsharpProjects = fsharpProjects |> List.map (fun x -> snd x.Value)

Assert.True (fsharpProjects |> List.forall (fun options -> not (checker.ProjectExistsInAnyCache options |> Async.RunSynchronously)))

fsharpProjects
|> List.iter (fun options ->
// We don't care about the results, we only care that it will create an incremental builder for each project when we make a call.
checker.TryParseAndCheckFileInProject(options, testFilePath, testFileSourceText, "CacheTests") |> Async.RunSynchronously |> ignore
)

// Projects should now exist in the cache.
Assert.True (fsharpProjects |> List.forall (fun options -> checker.ProjectExistsInAnyCache options |> Async.RunSynchronously))
(workspace, cenv)

[<Test>]
member __.ProjectCacheRemoveInvdividuallyBySolutionChanges () =
let (workspace, cenv) = basicTest ()
let checker = cenv.checker

let mutable solution = workspace.CurrentSolution
let projectIds = solution.ProjectIds
let fsharpProjects =
projectIds
|> Seq.map (fun projectId ->
let (_, _, options) = cenv.cache.[projectId]
options
) |> List.ofSeq

Assert.AreEqual (3, projectIds.Count)

for i = 0 to projectIds.Count - 1 do
solution <- solution.RemoveProject projectIds.[i]
FSharpCompilationHelpers.setSolution cenv solution

// Remaining projects should still be in the cache.
for i = i + 1 to projectIds.Count - 1 do
Assert.True (cenv.cache.ContainsKey projectIds.[i])
Assert.True (checker.ProjectExistsInAnyCache fsharpProjects.[i] |> Async.RunSynchronously)

Assert.False (cenv.cache.ContainsKey projectIds.[i])
Assert.False (checker.ProjectExistsInAnyCache fsharpProjects.[i] |> Async.RunSynchronously)

workspace.Dispose ()

[<Test>]
member __.ProjectCacheRemoveAllByNewSolution () =
let (workspace, cenv) = basicTest ()
let checker = cenv.checker

let solution = workspace.CurrentSolution
let projectIds = solution.ProjectIds
let fsharpProjects =
projectIds
|> Seq.map (fun projectId ->
let (_, _, options) = cenv.cache.[projectId]
options
) |> List.ofSeq

Assert.AreEqual (3, projectIds.Count)

workspace.ClearSolution ()
let solution = workspace.AddSolution (createSolutionInfoWithProjectCount 0)

FSharpCompilationHelpers.setSolution cenv solution

for i = 0 to projectIds.Count - 1 do
Assert.False (cenv.cache.ContainsKey projectIds.[i])
Assert.False (checker.ProjectExistsInAnyCache fsharpProjects.[i] |> Async.RunSynchronously)

workspace.Dispose ()


3 changes: 3 additions & 0 deletions vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@
<Compile Include="..\..\..\tests\service\TreeVisitorTests.fs">
<Link>CompilerService\TreeVisitorTests.fs</Link>
</Compile>
<Compile Include="CacheTests.fs">
<Link>Roslyn\CacheTests.fs</Link>
</Compile>
<Compile Include="ProjectOptionsBuilder.fs">
<Link>Roslyn\ProjectOptionsBuilder.fs</Link>
</Compile>
Expand Down