From 7497cd63b680118c3170c53814cd9076c54aa184 Mon Sep 17 00:00:00 2001 From: Will Smith Date: Mon, 26 Apr 2021 17:33:49 -0700 Subject: [PATCH 1/2] Adding last successful compilations for VS - C# -> F#. Allowing failure to occur on evaluating assembly contents. --- src/fsharp/CompilerImports.fs | 19 +++++--- .../FSharpProjectOptionsManager.fs | 45 ++++++++++++++++--- 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/fsharp/CompilerImports.fs b/src/fsharp/CompilerImports.fs index 8ee516b595e..c709e9ffcb8 100644 --- a/src/fsharp/CompilerImports.fs +++ b/src/fsharp/CompilerImports.fs @@ -1566,7 +1566,7 @@ and [] TcImports(tcConfigP: TcConfigProvider, initialResolutions: TcAsse phase2 // NOTE: When used in the Language Service this can cause the transitive checking of projects. Hence it must be cancellable. - member tcImports.RegisterAndPrepareToImportReferencedDll (ctok, r: AssemblyResolution) : Cancellable<_ * (unit -> AvailableImportedAssembly list)> = + member tcImports.TryRegisterAndPrepareToImportReferencedDll (ctok, r: AssemblyResolution) : Cancellable<(_ * (unit -> AvailableImportedAssembly list)) option> = cancellable { CheckDisposed() let m = r.originalReference.Range @@ -1578,6 +1578,12 @@ and [] TcImports(tcConfigP: TcConfigProvider, initialResolutions: TcAsse | None -> return None } + // If we have a project reference but did not get any valid contents, + // just return None and do not attempt to read elsewhere. + if contentsOpt.IsNone && r.ProjectReference.IsSome then + return None + else + let assemblyData = match contentsOpt with | Some ilb -> ilb @@ -1591,7 +1597,7 @@ and [] TcImports(tcConfigP: TcConfigProvider, initialResolutions: TcAsse if tcImports.IsAlreadyRegistered ilShortAssemName then let dllinfo = tcImports.FindDllInfo(ctok, m, ilShortAssemName) let phase2() = [tcImports.FindCcuInfo(ctok, m, ilShortAssemName, lookupOnly=true)] - return dllinfo, phase2 + return Some(dllinfo, phase2) else let dllinfo = { RawMetadata=assemblyData @@ -1616,7 +1622,7 @@ and [] TcImports(tcConfigP: TcConfigProvider, initialResolutions: TcAsse with e -> error(Error(FSComp.SR.buildErrorOpeningBinaryFile(filename, e.Message), m)) else tcImports.PrepareToImportReferencedILAssembly (ctok, m, filename, dllinfo) - return dllinfo, phase2 + return Some(dllinfo, phase2) } // NOTE: When used in the Language Service this can cause the transitive checking of projects. Hence it must be cancellable. @@ -1627,11 +1633,10 @@ and [] TcImports(tcConfigP: TcConfigProvider, initialResolutions: TcAsse nms |> Cancellable.each (fun nm -> cancellable { try - let! res = tcImports.RegisterAndPrepareToImportReferencedDll (ctok, nm) - return Some res + return! tcImports.TryRegisterAndPrepareToImportReferencedDll (ctok, nm) with e -> - errorR(Error(FSComp.SR.buildProblemReadingAssembly(nm.resolvedPath, e.Message), nm.originalReference.Range)) - return None + errorR(Error(FSComp.SR.buildProblemReadingAssembly(nm.resolvedPath, e.Message), nm.originalReference.Range)) + return None }) let dllinfos, phase2s = results |> List.choose id |> List.unzip diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs index 5419a315f37..7e2d5ef2dc3 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs @@ -106,24 +106,47 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor // This is used to not constantly emit the same compilation. let weakPEReferences = ConditionalWeakTable() + let lastSuccessfulCompilations = ConcurrentDictionary() let createPEReference (referencedProject: Project) (comp: Compilation) = + let projectId = referencedProject.Id + match weakPEReferences.TryGetValue comp with | true, fsRefProj -> fsRefProj | _ -> let weakComp = WeakReference(comp) let getStream = fun ct -> - match weakComp.TryGetTarget() with - | true, comp -> + let tryStream (comp: Compilation) = let ms = new MemoryStream() // do not dispose the stream as it will be owned on the reference. let emitOptions = Emit.EmitOptions(metadataOnly = true, includePrivateMembers = false, tolerateErrors = true) - comp.Emit(ms, options = emitOptions, cancellationToken = ct) |> ignore - ms.Position <- 0L - ms :> Stream - |> Some + try + let result = comp.Emit(ms, options = emitOptions, cancellationToken = ct) + + if result.Success then + lastSuccessfulCompilations.[projectId] <- comp + ms.Position <- 0L + ms :> Stream + |> Some + else + ms.Dispose() // it failed, dispose of stream + None + with + | _ -> + ms.Dispose() // it failed, dispose of stream + None + + let resultOpt = + match weakComp.TryGetTarget() with + | true, comp -> tryStream comp + | _ -> None + + match resultOpt with + | Some _ -> resultOpt | _ -> - None + match lastSuccessfulCompilations.TryGetValue(projectId) with + | true, comp -> tryStream comp + | _ -> None let fsRefProj = FSharpReferencedProject.CreatePortableExecutable( @@ -289,6 +312,12 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor projectOptions) checkerProvider.Checker.ClearCache(options, userOpName = "tryComputeOptions") + lastSuccessfulCompilations.ToArray() + |> Array.iter (fun pair -> + if not (currentSolution.ContainsProject(pair.Key)) then + lastSuccessfulCompilations.TryRemove(pair.Key) |> ignore + ) + checkerProvider.Checker.InvalidateConfiguration(projectOptions, startBackgroundCompile = false, userOpName = "computeOptions") let parsingOptions, _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions(projectOptions) @@ -365,6 +394,7 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor | FSharpProjectOptionsMessage.ClearOptions(projectId) -> match cache.TryRemove(projectId) with | true, (_, _, projectOptions) -> + lastSuccessfulCompilations.TryRemove(projectId) |> ignore checkerProvider.Checker.ClearCache([projectOptions]) | _ -> () @@ -372,6 +402,7 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor | FSharpProjectOptionsMessage.ClearSingleFileOptionsCache(documentId) -> match singleFileCache.TryRemove(documentId) with | true, (_, _, projectOptions) -> + lastSuccessfulCompilations.TryRemove(documentId.ProjectId) |> ignore checkerProvider.Checker.ClearCache([projectOptions]) | _ -> () From a53952f7fcb93780c59049f6f97c9340421d49f5 Mon Sep 17 00:00:00 2001 From: Will Smith Date: Tue, 27 Apr 2021 13:47:31 -0700 Subject: [PATCH 2/2] Clearing caches --- .../LanguageService/FSharpProjectOptionsManager.fs | 10 ++++++++++ .../FSharp.Editor/LanguageService/LanguageService.fs | 1 + 2 files changed, 11 insertions(+) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs index 7e2d5ef2dc3..5fafb1a1159 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs @@ -433,6 +433,13 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor | true, result -> Some(result) | _ -> None + member _.ClearAllCaches() = + cpsCommandLineOptions.Clear() + legacyProjectSites.Clear() + cache.Clear() + singleFileCache.Clear() + lastSuccessfulCompilations.Clear() + interface IDisposable with member _.Dispose() = cancellationTokenSource.Cancel() @@ -522,6 +529,9 @@ type internal FSharpProjectOptionsManager | Some (_, parsingOptions, _) -> parsingOptions | _ -> { FSharpParsingOptions.Default with IsInteractive = CompilerEnvironment.IsScriptFile document.Name } + member this.ClearAllCaches() = + reactor.ClearAllCaches() + [] /// This handles commandline change notifications from the Dotnet Project-system /// Prior to VS 15.7 path contained path to project file, post 15.7 contains target binpath diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index a4c4afbebc0..97224457cdc 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -62,6 +62,7 @@ type private FSharpSolutionEvents(projectManager: FSharpProjectOptionsManager) = member _.OnAfterCloseSolution(_) = projectManager.Checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() projectManager.MetadataAsSource.ClearGeneratedFiles() + projectManager.ClearAllCaches() VSConstants.S_OK member _.OnAfterLoadProject(_, _) = VSConstants.E_NOTIMPL