Skip to content
Merged
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
19 changes: 12 additions & 7 deletions src/fsharp/CompilerImports.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1566,7 +1566,7 @@ and [<Sealed>] 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
Expand All @@ -1578,6 +1578,12 @@ and [<Sealed>] 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
Expand All @@ -1591,7 +1597,7 @@ and [<Sealed>] 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
Expand All @@ -1616,7 +1622,7 @@ and [<Sealed>] 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.
Expand All @@ -1627,11 +1633,10 @@ and [<Sealed>] 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,24 +106,47 @@ type private FSharpProjectOptionsReactor (workspace: Workspace, settings: Editor

// This is used to not constantly emit the same compilation.
let weakPEReferences = ConditionalWeakTable<Compilation, FSharpReferencedProject>()
let lastSuccessfulCompilations = ConcurrentDictionary<ProjectId, Compilation>()

let createPEReference (referencedProject: Project) (comp: Compilation) =
let projectId = referencedProject.Id

match weakPEReferences.TryGetValue comp with
| true, fsRefProj -> fsRefProj
| _ ->
let weakComp = WeakReference<Compilation>(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(
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -365,13 +394,15 @@ 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])
| _ ->
()
legacyProjectSites.TryRemove(projectId) |> ignore
| FSharpProjectOptionsMessage.ClearSingleFileOptionsCache(documentId) ->
match singleFileCache.TryRemove(documentId) with
| true, (_, _, projectOptions) ->
lastSuccessfulCompilations.TryRemove(documentId.ProjectId) |> ignore
checkerProvider.Checker.ClearCache([projectOptions])
| _ ->
()
Expand Down Expand Up @@ -402,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()
Expand Down Expand Up @@ -491,6 +529,9 @@ type internal FSharpProjectOptionsManager
| Some (_, parsingOptions, _) -> parsingOptions
| _ -> { FSharpParsingOptions.Default with IsInteractive = CompilerEnvironment.IsScriptFile document.Name }

member this.ClearAllCaches() =
reactor.ClearAllCaches()

[<Export>]
/// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down